Introduce support for plugin-defined intrinsics in old JVM backend:

(Old is created first because all intrinsics emit bytecode anyway)

Provide intrinsic for serializer<T>() function so it won't
invoke typeOf() construction and KType->KSerializer conversion
making it fast and truly reflectionless

Add support for recalculating stack size in plugin-defined intrinsics
since it is needed for correct work:
Unify method for recalculating stack size with existing typeOf intrinsic

Add testdata for IR for future intrinsic in IR
This commit is contained in:
Leonid Startsev
2020-04-10 17:57:21 +03:00
committed by Space
parent 842f94443a
commit f9edbd825a
15 changed files with 1114 additions and 98 deletions
@@ -12,6 +12,9 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIALIZER_PROVIDER_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.getSerializableClassDescriptorByCompanion
import org.jetbrains.kotlinx.serialization.compiler.resolve.isKSerializer
import org.jetbrains.kotlinx.serialization.compiler.resolve.isSerializableObject
abstract class SerializableCompanionCodegen(
protected val companionDescriptor: ClassDescriptor,
@@ -20,15 +23,7 @@ abstract class SerializableCompanionCodegen(
protected val serializableDescriptor: ClassDescriptor = getSerializableClassDescriptorByCompanion(companionDescriptor)!!
open fun getSerializerGetterDescriptor(): FunctionDescriptor {
return companionDescriptor.unsubstitutedMemberScope.getContributedFunctions(
SERIALIZER_PROVIDER_NAME,
NoLookupLocation.FROM_BACKEND
).firstOrNull {
it.valueParameters.size == serializableDescriptor.declaredTypeParameters.size
&& it.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
&& it.valueParameters.all { p -> isKSerializer(p.type) }
&& it.returnType != null && isKSerializer(it.returnType)
} ?: throw IllegalStateException(
return findSerializerGetterOnCompanion(serializableDescriptor) ?: throw IllegalStateException(
"Can't find synthesized 'Companion.serializer()' function to generate, " +
"probably clash with user-defined function has occurred"
)
@@ -52,4 +47,19 @@ abstract class SerializableCompanionCodegen(
protected open fun generateLazySerializerGetter(methodDescriptor: FunctionDescriptor) {
generateSerializerGetter(methodDescriptor)
}
companion object {
fun findSerializerGetterOnCompanion(serializableDescriptor: ClassDescriptor): FunctionDescriptor? {
val companionObjectDesc = if (serializableDescriptor.isSerializableObject) serializableDescriptor else serializableDescriptor.companionObjectDescriptor
return companionObjectDesc?.unsubstitutedMemberScope?.getContributedFunctions(
SERIALIZER_PROVIDER_NAME,
NoLookupLocation.FROM_BACKEND
)?.firstOrNull {
it.valueParameters.size == serializableDescriptor.declaredTypeParameters.size
&& it.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
&& it.valueParameters.all { p -> isKSerializer(p.type) }
&& it.returnType != null && isKSerializer(it.returnType)
}
}
}
}
@@ -8,7 +8,9 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.jvm
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.context.ClassContext
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
@@ -247,56 +249,88 @@ internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializer(
}
}
internal fun AbstractSerialGenerator?.stackValueSerializerInstance(
expressionCodegen: ExpressionCodegen, codegen: ClassBodyCodegen, module: ModuleDescriptor, kType: KotlinType, maybeSerializer: ClassDescriptor?,
iv: InstructionAdapter?,
genericIndex: Int? = null,
genericSerializerFieldGetter: (InstructionAdapter.(Int, KotlinType) -> Unit)? = null
): Boolean {
return stackValueSerializerInstance(
expressionCodegen,codegen.typeMapper,
module,
kType,
maybeSerializer,
iv,
genericIndex,
false,
genericSerializerFieldGetter
)
}
// returns false is cannot not use serializer
// use iv == null to check only (do not emit serializer onto stack)
internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCodegen: ExpressionCodegen, classCodegen: ClassBodyCodegen, module: ModuleDescriptor, kType: KotlinType, maybeSerializer: ClassDescriptor?,
iv: InstructionAdapter?,
genericIndex: Int? = null,
genericSerializerFieldGetter: (InstructionAdapter.(Int, KotlinType) -> Unit)? = null
internal fun AbstractSerialGenerator?.stackValueSerializerInstance(
expressionCodegen: ExpressionCodegen?, typeMapper: KotlinTypeMapper, module: ModuleDescriptor, kType: KotlinType, maybeSerializer: ClassDescriptor?,
iv: InstructionAdapter?,
genericIndex: Int? = null,
insertExceptionOnNoSerializer: Boolean = false,
genericSerializerFieldGetter: (InstructionAdapter.(Int, KotlinType) -> Unit)? = null,
): Boolean {
if (maybeSerializer == null && genericIndex != null) {
// get field from serializer object
iv?.run { genericSerializerFieldGetter?.invoke(this, genericIndex, kType) }
return true
}
val serializer = maybeSerializer ?: return false
val serializer = maybeSerializer ?: run {
if (insertExceptionOnNoSerializer) iv?.apply {
aconst(kType.getJetTypeFqName(false))
invokestatic(
"kotlinx/serialization/SerializersKt",
"noCompiledSerializer",
"(Ljava/lang/String;)Lkotlinx/serialization/KSerializer;",
false
)
}
return false
}
if (serializer.kind == ClassKind.OBJECT) {
// singleton serializer -- just get it
if (iv != null)
StackValue.singleton(serializer, classCodegen.typeMapper).put(kSerializerType, iv)
StackValue.singleton(serializer, typeMapper).put(kSerializerType, iv)
return true
}
// serializer is not singleton object and shall be instantiated
val argSerializers = kType.arguments.map { projection ->
// bail out from stackValueSerializerInstance if any type argument is not serializable
// check if any type argument is not serializable
val argType = projection.type
val argSerializer = if (argType.isTypeParameter()) null else {
findTypeSerializerOrContext(module, argType, sourceElement = classCodegen.descriptor.findPsi())
?: return false
}
val argSerializer =
if (argType.isTypeParameter()) null else findTypeSerializerOrContextUnchecked(module, argType)
// check if it can be properly serialized with its args recursively
if (!stackValueSerializerInstance(
expressionCodegen,
classCodegen,
typeMapper,
module,
argType,
argSerializer,
null,
argType.genericIndex,
insertExceptionOnNoSerializer = false,
genericSerializerFieldGetter
)
)
return false
) {
// bail out only if we do not need to insert exception
if (!insertExceptionOnNoSerializer) return false
}
Pair(argType, argSerializer)
}
// new serializer if needed
iv?.apply {
val serializerType = classCodegen.typeMapper.mapClass(serializer)
val serializerType = typeMapper.mapClass(serializer)
val classDescriptor = kType.toClassDescriptor!!
if (serializer.classId == enumSerializerId && !classDescriptor.useGeneratedEnumSerializer) {
// runtime contains enum serializer factory functions
val javaEnumArray = Type.getType("[Ljava/lang/Enum;")
val enumJavaType = classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)
val enumJavaType = typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)
val serialName = classDescriptor.serialName()
if (classDescriptor.isEnumWithSerialInfoAnnotation()) {
@@ -323,7 +357,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
} else {
fillArray(annotationType, annotations) { _, annotation ->
val (annotationClass, args, consParams) = annotation
expressionCodegen.generateSyntheticAnnotationOnStack(annotationClass, args, consParams)
expressionCodegen?.generateSyntheticAnnotationOnStack(annotationClass, args, consParams)
}
}
}
@@ -361,14 +395,15 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
assert(
stackValueSerializerInstance(
expressionCodegen,
classCodegen,
typeMapper,
module,
argType,
argSerializer,
this,
argType.genericIndex,
insertExceptionOnNoSerializer,
genericSerializerFieldGetter
)
) || insertExceptionOnNoSerializer
)
// wrap into nullable serializer if argType is nullable
if (argType.isMarkedNullable) wrapStackValueIntoNullableSerializer()
@@ -381,16 +416,16 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
// support legacy serializer instantiation by constructor for old runtimes
aconst(serialName)
signature.append("Ljava/lang/String;")
val enumJavaType = classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)
val enumJavaType = typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)
val javaEnumArray = Type.getType("[Ljava/lang/Enum;")
invokestatic(enumJavaType.internalName, "values","()[${enumJavaType.descriptor}", false)
invokestatic(enumJavaType.internalName, "values", "()[${enumJavaType.descriptor}", false)
checkcast(javaEnumArray)
signature.append(javaEnumArray.descriptor)
}
contextSerializerId, polymorphicSerializerId -> {
// a special way to instantiate enum -- need a enum KClass reference
// GENERIC_ARGUMENT forces boxing in order to obtain KClass
aconst(classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT))
aconst(typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT))
AsmUtil.wrapJavaClassIntoKClass(this)
signature.append(AsmTypes.K_CLASS_TYPE.descriptor)
if (serializer.classId == contextSerializerId && serializer.constructors.any { it.valueParameters.size == 3 }) {
@@ -410,7 +445,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
}
referenceArraySerializerId -> {
// a special way to instantiate reference array serializer -- need an element KClass reference
aconst(classCodegen.typeMapper.mapType(kType.arguments[0].type, null, TypeMappingMode.GENERIC_ARGUMENT))
aconst(typeMapper.mapType(kType.arguments[0].type, null, TypeMappingMode.GENERIC_ARGUMENT))
AsmUtil.wrapJavaClassIntoKClass(this)
signature.append(AsmTypes.K_CLASS_TYPE.descriptor)
// Reference array serializer still needs serializer for its argument type
@@ -419,13 +454,13 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
sealedSerializerId -> {
aconst(serialName)
signature.append("Ljava/lang/String;")
aconst(classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT))
aconst(typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT))
AsmUtil.wrapJavaClassIntoKClass(this)
signature.append(AsmTypes.K_CLASS_TYPE.descriptor)
val (subClasses, subSerializers) = allSealedSerializableSubclassesFor(kType.toClassDescriptor!!, module)
// KClasses vararg
fillArray(AsmTypes.K_CLASS_TYPE, subClasses) { _, type ->
aconst(classCodegen.typeMapper.mapType(type, null, TypeMappingMode.GENERIC_ARGUMENT))
aconst(typeMapper.mapType(type, null, TypeMappingMode.GENERIC_ARGUMENT))
AsmUtil.wrapJavaClassIntoKClass(this)
}
signature.append(AsmTypes.K_CLASS_ARRAY_TYPE.descriptor)
@@ -435,7 +470,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
assert(
stackValueSerializerInstance(
expressionCodegen,
classCodegen,
typeMapper,
module,
argType,
argSerializer,
@@ -446,11 +481,12 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
assert(
stackValueSerializerInstance(
expressionCodegen,
classCodegen,
typeMapper,
module,
(genericType.constructor.declarationDescriptor as TypeParameterDescriptor).representativeUpperBound,
module.getClassFromSerializationPackage(SpecialBuiltins.polymorphicSerializer),
this
this,
insertExceptionOnNoSerializer = insertExceptionOnNoSerializer
)
)
}
@@ -462,7 +498,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCode
objectSerializerId -> {
aconst(serialName)
signature.append("Ljava/lang/String;")
StackValue.singleton(kType.toClassDescriptor!!, classCodegen.typeMapper).put(Type.getType("Ljava/lang/Object;"), iv)
StackValue.singleton(kType.toClassDescriptor!!, typeMapper).put(Type.getType("Ljava/lang/Object;"), iv)
signature.append("Ljava/lang/Object;")
}
// all serializers get arguments with serializers of their generic types
@@ -0,0 +1,124 @@
/*
* 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.jvm
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.inline.newMethodNodeWithCorrectStackSize
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.ir.expressions.typeParametersCount
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
import org.jetbrains.kotlinx.serialization.compiler.backend.common.AbstractSerialGenerator
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCompanionCodegen
import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContextUnchecked
import org.jetbrains.kotlinx.serialization.compiler.resolve.isSerializableObject
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.InsnList
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
object JvmSerializerIntrinsic {
fun applyFunction(resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val targetFunction = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return null
val isSerializerReifiedFunction =
targetFunction.fqNameSafe.asString() == "kotlinx.serialization.serializer"
&& targetFunction.valueParameters.isEmpty()
&& targetFunction.typeParametersCount == 1
&& targetFunction.dispatchReceiverParameter == null
&& targetFunction.extensionReceiverParameter == null
if (!isSerializerReifiedFunction) return null
val typeArgument =
resolvedCall.typeArguments.entries.singleOrNull()?.value ?: error("serializer() function has exactly one type parameter")
return StackValue.functionCall(kSerializerType, targetFunction.returnType) { iv ->
generateSerializerForType(typeArgument, iv, c.typeMapper, c.codegen.typeSystem, module = c.codegen.state.module)
}
}
fun applyPluginDefinedReifiedOperationMarker(
insn: MethodInsnNode,
instructions: InsnList,
type: KotlinType,
typeMapper: KotlinTypeMapper,
typeSystem: TypeSystemCommonBackendContext,
module: ModuleDescriptor
): Int {
val newMethodNode = newMethodNodeWithCorrectStackSize {
generateSerializerForType(type, it, typeMapper, typeSystem, module)
}
instructions.remove(insn.next)
instructions.insert(insn, newMethodNode.instructions)
return newMethodNode.maxStack
}
private fun InstructionAdapter.putReifyMarkerIfNeeded(type: KotlinType, typeSystem: TypeSystemCommonBackendContext): Boolean {
val typeDescriptor = (type as SimpleType).constructor.declarationDescriptor!!
if (typeDescriptor is TypeParameterDescriptor) { // need further reification
ReifiedTypeInliner.putReifiedOperationMarkerIfNeeded(
typeDescriptor,
false,
ReifiedTypeInliner.OperationKind.PLUGIN_DEFINED,
this,
typeSystem
)
invokestatic("kotlinx/serialization/SerializersKt", "serializer", "()Lkotlinx/serialization/KSerializer;", false)
return true
}
return false
}
private fun generateSerializerForType(
type: KotlinType,
adapter: InstructionAdapter,
typeMapper: KotlinTypeMapper,
typeSystem: TypeSystemCommonBackendContext,
module: ModuleDescriptor
): Unit = with(adapter) {
if (putReifyMarkerIfNeeded(type, typeSystem)) return
val typeDescriptor = (type as SimpleType).constructor.declarationDescriptor!! as ClassDescriptor
val serializerMethod = SerializableCompanionCodegen.findSerializerGetterOnCompanion(typeDescriptor)
if (serializerMethod != null) {
// fast path
val companionType = if (typeDescriptor.isSerializableObject) typeDescriptor else typeDescriptor.companionObjectDescriptor!!
StackValue.singleton(companionType, typeMapper).put(this)
val args = type.arguments.map { it.type }
args.forEach { generateSerializerForType(it, this, typeMapper, typeSystem, module) }
val signature = kSerializerType.descriptor.repeat(args.size)
invokevirtual(
typeMapper.mapType(companionType).internalName,
"serializer",
"(${signature})${kSerializerType.descriptor}",
false
)
} else {
// More general path, including special ol built-in serializers for e.g. List
val emptyGenerator: AbstractSerialGenerator? = null // stub indicating we do not look into @UseSerializers to avoid confusion
val serializer = emptyGenerator.findTypeSerializerOrContextUnchecked(module, type)
emptyGenerator.stackValueSerializerInstance(
null,
typeMapper,
module,
type,
serializer,
this,
insertExceptionOnNoSerializer = true
) { _, genericArg ->
assert(putReifyMarkerIfNeeded(genericArg, typeSystem))
}
if (type.isMarkedNullable) wrapStackValueIntoNullableSerializer()
}
}
}
@@ -1,27 +1,22 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.extensions
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerialInfoCodegenImpl
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializableCodegenImpl
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializableCompanionCodegenImpl
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.InsnList
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
open class SerializationCodegenExtension @JvmOverloads constructor(val metadataPlugin: SerializationDescriptorSerializerPlugin? = null) : ExpressionCodegenExtension {
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
@@ -31,6 +26,22 @@ open class SerializationCodegenExtension @JvmOverloads constructor(val metadataP
SerializableCompanionCodegenImpl.generateSerializableExtensions(codegen)
}
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
return JvmSerializerIntrinsic.applyFunction(resolvedCall, c)
}
override fun applyPluginDefinedReifiedOperationMarker(
insn: MethodInsnNode,
instructions: InsnList,
type: KotlinType,
asmType: Type,
typeMapper: KotlinTypeMapper,
typeSystem: TypeSystemCommonBackendContext,
module: ModuleDescriptor
): Int {
return JvmSerializerIntrinsic.applyPluginDefinedReifiedOperationMarker(insn, instructions, type, typeMapper, typeSystem, module)
}
override val shouldGenerateClassSyntheticPartsInLightClassesMode: Boolean
get() = false
}