diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index f05759b1a98..c46c3929030 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -128,7 +128,7 @@ class JvmBackendContext( val inlineClassReplacements = MemoizedInlineClassReplacements(state.functionsWithInlineClassReturnTypesMangled, irFactory, this) - internal val continuationClassesVarsCountByType: MutableMap> = hashMapOf() + internal val continuationClassesVarsCountByType: MutableMap> = hashMapOf() internal fun referenceClass(descriptor: ClassDescriptor): IrClassSymbol = symbolTable.lazyWrapper.referenceClass(descriptor) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index 58625a6291b..01a5d8c4541 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -359,11 +359,14 @@ class ClassCodegen private constructor( val continuationClass = method.continuationClass() // null if `SuspendLambda.invokeSuspend` - `this` is continuation itself val continuationClassCodegen = lazy { if (continuationClass != null) getOrCreate(continuationClass, context, method) else this } + // For suspend lambdas continuation class is null, and we need to use containing class to put L$ fields + val attributeContainer = continuationClass?.attributeOwnerId ?: irClass.attributeOwnerId + node.acceptWithStateMachine( method, this, smapCopyingVisitor, - context.continuationClassesVarsCountByType[continuationClass] ?: emptyMap() + context.continuationClassesVarsCountByType[attributeContainer] ?: emptyMap() ) { continuationClassCodegen.value.visitor } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 new file mode 100644 index 00000000000..134d805adca --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 @@ -0,0 +1,507 @@ +/* + * Copyright 2010-2020 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.kotlin.backend.jvm.codegen + +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry +import org.jetbrains.kotlin.backend.jvm.lower.buildAssertionsDisabledField +import org.jetbrains.kotlin.backend.jvm.lower.hasAssertionsDisabledField +import org.jetbrains.kotlin.codegen.DescriptorAsmUtil +import org.jetbrains.kotlin.codegen.inline.* +import org.jetbrains.kotlin.codegen.writeKotlinMetadata +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.DescriptorVisibility +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionReference +import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.protobuf.MessageLite +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.VOLATILE_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.checkers.JvmSimpleNameBacktickChecker +import org.jetbrains.kotlin.resolve.jvm.diagnostics.* +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.commons.Method +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import java.io.File + +interface MetadataSerializer { + fun serialize(metadata: MetadataSource): Pair? + fun bindMethodMetadata(metadata: MetadataSource.Property, signature: Method) + fun bindMethodMetadata(metadata: MetadataSource.Function, signature: Method) + fun bindFieldMetadata(metadata: MetadataSource.Property, signature: Pair) +} + +class ClassCodegen private constructor( + val irClass: IrClass, + val context: JvmBackendContext, + private val parentFunction: IrFunction?, +) : InnerClassConsumer { + private val parentClassCodegen = (parentFunction?.parentAsClass ?: irClass.parent as? IrClass)?.let { getOrCreate(it, context) } + private val withinInline: Boolean = parentClassCodegen?.withinInline == true || parentFunction?.isInline == true + + private val state get() = context.state + private val typeMapper get() = context.typeMapper + + val type: Type = typeMapper.mapClass(irClass) + + val reifiedTypeParametersUsages = ReifiedTypeParametersUsages() + + private val jvmSignatureClashDetector = JvmSignatureClashDetector(irClass, type, context) + + private val classOrigin = irClass.descriptorOrigin + + private val visitor = state.factory.newVisitor(classOrigin, type, irClass.fileParent.loadSourceFilesInfo()).apply { + val signature = typeMapper.mapClassSignature(irClass, type) + // Ensure that the backend only produces class names that would be valid in the frontend for JVM. + if (context.state.classBuilderMode.generateBodies && signature.hasInvalidName()) { + throw IllegalStateException("Generating class with invalid name '${type.className}': ${irClass.dump()}") + } + defineClass( + irClass.psiElement, + state.classFileVersion, + irClass.flags, + signature.name, + signature.javaGenericSignature, + signature.superclassName, + signature.interfaces.toTypedArray() + ) + } + + // TODO: the order of entries in this set depends on the order in which methods are generated; this means it is unstable + // under incremental compilation, as calls to `inline fun`s declared in this class cause them to be generated out of order. + private val innerClasses = linkedSetOf() + + // TODO: the names produced by generators in this map depend on the order in which methods are generated; see above. + private val regeneratedObjectNameGenerators = mutableMapOf() + + fun getRegeneratedObjectNameGenerator(function: IrFunction): NameGenerator { + val name = if (function.name.isSpecial) "special" else function.name.asString() + return regeneratedObjectNameGenerators.getOrPut(name) { + NameGenerator("${type.internalName}\$$name\$\$inlined") + } + } + + private var generated = false + + fun generate() { + // TODO: reject repeated generate() calls; currently, these can happen for objects in finally + // blocks since they are `accept`ed once per each CFG edge out of the try-finally. + if (generated) return + generated = true + + // We remove reads of `$$delegatedProperties` (and the field itself) if they are not in fact used for anything. + val delegatedProperties = irClass.fields.singleOrNull { it.origin == JvmLoweredDeclarationOrigin.GENERATED_PROPERTY_REFERENCE } + val delegatedPropertyOptimizer = if (delegatedProperties != null) DelegatedPropertyOptimizer() else null + // Generating a method node may cause the addition of a field with an initializer if an inline function + // call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors, + // there is a very specific generation order. + val smap = context.getSourceMapper(irClass) + // 1. Any method other than `` can add a field and a `` statement: + for (method in irClass.declarations.filterIsInstance()) { + if (method.name.asString() != "") { + generateMethod(method, smap, delegatedPropertyOptimizer) + } + } + // 2. `` itself can add a field, but the statement is generated via the `return init` hack: + irClass.functions.find { it.name.asString() == "" }?.let { generateMethod(it, smap, delegatedPropertyOptimizer) } + // 3. Now we have all the fields (`$$delegatedProperties` might be redundant if all reads were optimized out): + for (field in irClass.fields) { + if (field !== delegatedProperties || delegatedPropertyOptimizer?.needsDelegatedProperties == true) { + generateField(field) + } + } + // 4. Generate nested classes at the end, to ensure that when the companion's metadata is serialized + // everything moved to the outer class has already been recorded in `globalSerializationBindings`. + for (declaration in irClass.declarations) { + if (declaration is IrClass) { + getOrCreate(declaration, context).generate() + } + } + + object : AnnotationCodegen(this@ClassCodegen, context) { + override fun visitAnnotation(descr: String?, visible: Boolean): AnnotationVisitor { + return visitor.visitor.visitAnnotation(descr, visible) + } + }.genAnnotations(irClass, null, null) + generateKotlinMetadataAnnotation() + + generateInnerAndOuterClasses() + + if (withinInline || !smap.isTrivial) { + visitor.visitSMAP(smap, !context.state.languageVersionSettings.supportsFeature(LanguageFeature.CorrectSourceMappingSyntax)) + } else { + smap.sourceInfo!!.sourceFileName?.let { + visitor.visitSource(it, null) + } + } + + visitor.done() + jvmSignatureClashDetector.reportErrors(classOrigin) + } + + fun generateAssertFieldIfNeeded(generatingClInit: Boolean): IrExpression? { + if (irClass.hasAssertionsDisabledField(context)) + return null + val topLevelClass = generateSequence(this) { it.parentClassCodegen }.last().irClass + val field = irClass.buildAssertionsDisabledField(context, topLevelClass) + generateField(field) + // Normally, `InitializersLowering` would move the initializer to , but + // it's obviously too late for that. + val init = IrSetFieldImpl( + field.startOffset, field.endOffset, field.symbol, null, + field.initializer!!.expression, context.irBuiltIns.unitType + ) + if (generatingClInit) { + // Too late to modify the IR; have to ask the currently active `ExpressionCodegen` + // to generate this statement directly. + return init + } + val classInitializer = irClass.functions.singleOrNull { it.name.asString() == "" } ?: irClass.addFunction { + name = Name.special("") + returnType = context.irBuiltIns.unitType + }.apply { + body = IrBlockBodyImpl(startOffset, endOffset) + } + (classInitializer.body as IrBlockBody).statements.add(0, init) + return null + } + + private val metadataSerializer: MetadataSerializer = + context.backendExtension.createSerializer( + context, irClass, type, visitor.serializationBindings, parentClassCodegen?.metadataSerializer + ) + + private fun generateKotlinMetadataAnnotation() { + // TODO: if `-Xmultifile-parts-inherit` is enabled, write the corresponding flag for parts and facades to [Metadata.extraInt]. + val extraFlags = context.backendExtension.generateMetadataExtraFlags(state.abiStability) + + val facadeClassName = context.multifileFacadeForPart[irClass.attributeOwnerId] + val metadata = irClass.metadata + val entry = irClass.fileParent.fileEntry + val kind = when { + facadeClassName != null -> KotlinClassHeader.Kind.MULTIFILE_CLASS_PART + metadata is MetadataSource.Class -> KotlinClassHeader.Kind.CLASS + metadata is MetadataSource.File -> KotlinClassHeader.Kind.FILE_FACADE + metadata is MetadataSource.Function -> KotlinClassHeader.Kind.SYNTHETIC_CLASS + entry is MultifileFacadeFileEntry -> KotlinClassHeader.Kind.MULTIFILE_CLASS + else -> KotlinClassHeader.Kind.SYNTHETIC_CLASS + } + writeKotlinMetadata(visitor, state, kind, extraFlags) { + if (metadata != null) { + metadataSerializer.serialize(metadata)?.let { (proto, stringTable) -> + DescriptorAsmUtil.writeAnnotationData(it, proto, stringTable) + } + } + + if (entry is MultifileFacadeFileEntry) { + val arv = it.visitArray(JvmAnnotationNames.METADATA_DATA_FIELD_NAME) + for (partFile in entry.partFiles) { + val fileClass = partFile.declarations.singleOrNull { it.isFileClass } as IrClass? + if (fileClass != null) arv.visit(null, typeMapper.mapClass(fileClass).internalName) + } + arv.visitEnd() + } + + if (facadeClassName != null) { + it.visit(JvmAnnotationNames.METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME, facadeClassName.internalName) + } + + if (irClass in context.classNameOverride) { + val isFileClass = kind == KotlinClassHeader.Kind.MULTIFILE_CLASS || + kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART || + kind == KotlinClassHeader.Kind.FILE_FACADE + assert(isFileClass) { "JvmPackageName is not supported for classes: ${irClass.render()}" } + it.visit(JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME, irClass.fqNameWhenAvailable!!.parent().asString()) + } + } + } + + private fun IrFile.loadSourceFilesInfo(): List { + val entry = fileEntry + if (entry is MultifileFacadeFileEntry) { + return entry.partFiles.flatMap { it.loadSourceFilesInfo() } + } + return listOfNotNull(context.psiSourceManager.getFileEntry(this)?.let { File(it.name) }) + } + + companion object { + fun getOrCreate( + irClass: IrClass, + context: JvmBackendContext, + // The `parentFunction` is only set for classes nested inside of functions. This is usually safe, since there is no + // way to refer to (inline) members of such a class from outside of the function unless the function in question is + // itself declared as inline. In that case, the function will be compiled before we can refer to the nested class. + // + // The one exception to this rule are anonymous objects defined as members of a class. These are nested inside of the + // class initializer, but can be referred to from anywhere within the scope of the class. That's why we have to ensure + // that all references to classes inside of have a non-null `parentFunction`. + parentFunction: IrFunction? = irClass.parent.safeAs()?.takeIf { + it.origin == JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER + }, + ): ClassCodegen = + context.classCodegens.getOrPut(irClass) { ClassCodegen(irClass, context, parentFunction) }.also { + assert(parentFunction == null || it.parentFunction == parentFunction) { + "inconsistent parent function for ${irClass.render()}:\n" + + "New: ${parentFunction!!.render()}\n" + + "Old: ${it.parentFunction?.render()}" + } + } + + private fun JvmClassSignature.hasInvalidName() = + name.splitToSequence('/').any { identifier -> identifier.any { it in JvmSimpleNameBacktickChecker.INVALID_CHARS } } + } + + private fun generateField(field: IrField) { + val fieldType = typeMapper.mapType(field) + val fieldSignature = + if (field.origin == IrDeclarationOrigin.PROPERTY_DELEGATE) null + else context.methodSignatureMapper.mapFieldSignature(field) + val fieldName = field.name.asString() + val flags = field.computeFieldFlags(context, state.languageVersionSettings) + val fv = visitor.newField( + field.descriptorOrigin, flags, fieldName, fieldType.descriptor, + fieldSignature, (field.initializer?.expression as? IrConst<*>)?.value + ) + + jvmSignatureClashDetector.trackField(field, RawSignature(fieldName, fieldType.descriptor, MemberKind.FIELD)) + + if (field.origin != JvmLoweredDeclarationOrigin.CONTINUATION_CLASS_RESULT_FIELD) { + val skipNullabilityAnnotations = + flags and (Opcodes.ACC_SYNTHETIC or Opcodes.ACC_ENUM) != 0 || + field.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_STATIC_CALLABLE_REFERENCE_INSTANCE + object : AnnotationCodegen(this@ClassCodegen, context, skipNullabilityAnnotations) { + override fun visitAnnotation(descr: String?, visible: Boolean): AnnotationVisitor { + return fv.visitAnnotation(descr, visible) + } + + override fun visitTypeAnnotation(descr: String?, path: TypePath?, visible: Boolean): AnnotationVisitor { + return fv.visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.FIELD).value, path, descr, visible) + } + }.genAnnotations(field, fieldType, field.type) + } + + (field.metadata as? MetadataSource.Property)?.let { + metadataSerializer.bindFieldMetadata(it, fieldType to fieldName) + } + + if (irClass.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) && !field.isStatic) { + // TODO: Write annotations to the component + // visitor.newRecordComponent(fieldName, fieldType.descriptor, fieldSignature) + } + } + + private val generatedInlineMethods = mutableMapOf() + + fun generateMethodNode(method: IrFunction): SMAPAndMethodNode { + if (!method.isInline && !method.isSuspendCapturingCrossinline()) { + // Inline methods can be used multiple times by `IrSourceCompilerForInline`, suspend methods + // are used twice (`f` and `f$$forInline`) if they capture crossinline lambdas, and everything + // else is only generated by `generateMethod` below so does not need caching. + // TODO: inline lambdas are not marked `isInline`, and are generally used once, but may be needed + // multiple times if declared in a `finally` block - should they be cached? + return FunctionCodegen(method, this).generate() + } + val (node, smap) = generatedInlineMethods.getOrPut(method) { FunctionCodegen(method, this).generate() } + val copy = with(node) { MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions.toTypedArray()) } + node.instructions.resetLabels() + node.accept(copy) + return SMAPAndMethodNode(copy, smap) + } + + private fun generateMethod(method: IrFunction, classSMAP: SourceMapper, delegatedPropertyOptimizer: DelegatedPropertyOptimizer?) { + if (method.isFakeOverride) { + jvmSignatureClashDetector.trackFakeOverrideMethod(method) + return + } + + val (node, smap) = generateMethodNode(method) + if (delegatedPropertyOptimizer != null) { + delegatedPropertyOptimizer.transform(node) + if (method.name.asString() == "") { + delegatedPropertyOptimizer.transformClassInitializer(node) + } + } + node.preprocessSuspendMarkers( + method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(), + method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE + ) + val mv = with(node) { visitor.newMethod(method.descriptorOrigin, access, name, desc, signature, exceptions.toTypedArray()) } + val smapCopier = SourceMapCopier(classSMAP, smap) + val smapCopyingVisitor = object : MethodVisitor(Opcodes.API_VERSION, mv) { + override fun visitLineNumber(line: Int, start: Label) = + super.visitLineNumber(smapCopier.mapLineNumber(line), start) + } + if (method.hasContinuation()) { + // Generate a state machine within this method. The continuation class for it should be generated + // lazily so that if tail call optimization kicks in, the unused class will not be written to the output. + val continuationClass = method.continuationClass() // null if `SuspendLambda.invokeSuspend` - `this` is continuation itself + val continuationClassCodegen = lazy { if (continuationClass != null) getOrCreate(continuationClass, context, method) else this } + + // For suspend lambdas continuation class is null, and we need to use containing class to put L$ fields + val attributeContainer = continuationClass?.attributeOwnerId ?: irClass.attributeOwnerId + + node.acceptWithStateMachine( + method, + this, + smapCopyingVisitor, + context.continuationClassesVarsCountByType[attributeContainer] ?: emptyMap() + ) { + continuationClassCodegen.value.visitor + } + + if (continuationClass != null && (continuationClassCodegen.isInitialized() || method.isSuspendCapturingCrossinline())) { + continuationClassCodegen.value.generate() + } + } else { + node.accept(smapCopyingVisitor) + } + jvmSignatureClashDetector.trackMethod(method, RawSignature(node.name, node.desc, MemberKind.METHOD)) + + when (val metadata = method.metadata) { + is MetadataSource.Property -> { + assert(method.origin == JvmLoweredDeclarationOrigin.SYNTHETIC_METHOD_FOR_PROPERTY_OR_TYPEALIAS_ANNOTATIONS) { + "MetadataSource.Property on IrFunction should only be used for synthetic \$annotations methods: ${method.render()}" + } + metadataSerializer.bindMethodMetadata(metadata, Method(node.name, node.desc)) + } + is MetadataSource.Function -> metadataSerializer.bindMethodMetadata(metadata, Method(node.name, node.desc)) + null -> Unit + else -> error("Incorrect metadata source $metadata for:\n${method.dump()}") + } + } + + private fun generateInnerAndOuterClasses() { + // JVMS7 (4.7.6): a nested class or interface member will have InnerClasses information + // for each enclosing class and for each immediate member + parentClassCodegen?.innerClasses?.add(irClass) + for (codegen in generateSequence(this) { it.parentClassCodegen }.takeWhile { it.parentClassCodegen != null }) { + innerClasses.add(codegen.irClass) + } + + // JVMS7 (4.7.7): A class must have an EnclosingMethod attribute if and only if + // it is a local class or an anonymous class. + // + // The attribute contains the innermost class that encloses the declaration of + // the current class. If the current class is immediately enclosed by a method + // or constructor, the name and type of the function is recorded as well. + if (parentClassCodegen != null) { + // In case there's no primary constructor, it's unclear which constructor should be the enclosing one, so we select the first. + val enclosingFunction = if (irClass.attributeOwnerId in context.isEnclosedInConstructor) { + val containerClass = parentClassCodegen.irClass + containerClass.primaryConstructor + ?: containerClass.declarations.firstIsInstanceOrNull() + ?: error("Class in a non-static initializer found, but container has no constructors: ${containerClass.render()}") + } else parentFunction + if (enclosingFunction != null || irClass.isAnonymousObject) { + val method = enclosingFunction?.let(context.methodSignatureMapper::mapAsmMethod) + visitor.visitOuterClass(parentClassCodegen.type.internalName, method?.name, method?.descriptor) + } + } + + for (klass in innerClasses) { + val innerClass = typeMapper.classInternalName(klass) + val outerClass = + if (klass.attributeOwnerId in context.isEnclosedInConstructor) null + else klass.parent.safeAs()?.let(typeMapper::classInternalName) + val innerName = klass.name.takeUnless { it.isSpecial }?.asString() + visitor.visitInnerClass(innerClass, outerClass, innerName, klass.calculateInnerClassAccessFlags(context)) + } + } + + override fun addInnerClassInfoFromAnnotation(innerClass: IrClass) { + // It's necessary for proper recovering of classId by plain string JVM descriptor when loading annotations + // See FileBasedKotlinClass.convertAnnotationVisitor + generateSequence(innerClass) { it.parent as? IrDeclaration }.takeWhile { !it.isTopLevelDeclaration }.forEach { + if (it is IrClass) { + innerClasses.add(it) + } + } + } + + private val IrDeclaration.descriptorOrigin: JvmDeclarationOrigin + get() { + val psiElement = context.psiSourceManager.findPsiElement(this) + // For declarations inside lambdas, produce a descriptor which refers back to the original function. + // This is needed for plugins which check for lambdas inside of inline functions using the descriptor + // contained in JvmDeclarationOrigin. This matches the behavior of the JVM backend. + // TODO: this is really not very useful, as this does nothing for other anonymous objects. + val isLambda = irClass.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL || + irClass.origin == JvmLoweredDeclarationOrigin.SUSPEND_LAMBDA + val descriptor = if (isLambda) + irClass.attributeOwnerId.safeAs()?.symbol?.owner?.toIrBasedDescriptor() ?: toIrBasedDescriptor() + else + toIrBasedDescriptor() + return if (origin == IrDeclarationOrigin.FILE_CLASS) + JvmDeclarationOrigin(JvmDeclarationOriginKind.PACKAGE_PART, psiElement, descriptor) + else + OtherOrigin(psiElement, descriptor) + } +} + +private val IrClass.flags: Int + get() = origin.flags or getVisibilityAccessFlagForClass() or + (if (isAnnotatedWithDeprecated) Opcodes.ACC_DEPRECATED else 0) or + (if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME)) Opcodes.ACC_SYNTHETIC else 0) or + when { + isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT + isInterface -> Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT + isEnumClass -> Opcodes.ACC_ENUM or Opcodes.ACC_SUPER or modality.flags + else -> Opcodes.ACC_SUPER or modality.flags + } + +private fun IrField.computeFieldFlags(context: JvmBackendContext, languageVersionSettings: LanguageVersionSettings): Int = + origin.flags or visibility.flags or + (if (isDeprecatedCallable(context) || + correspondingPropertySymbol?.owner?.isDeprecatedCallable(context) == true + ) Opcodes.ACC_DEPRECATED else 0) or + (if (isFinal) Opcodes.ACC_FINAL else 0) or + (if (isStatic) Opcodes.ACC_STATIC else 0) or + (if (hasAnnotation(VOLATILE_ANNOTATION_FQ_NAME)) Opcodes.ACC_VOLATILE else 0) or + (if (hasAnnotation(TRANSIENT_ANNOTATION_FQ_NAME)) Opcodes.ACC_TRANSIENT else 0) or + (if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) || + isPrivateCompanionFieldInInterface(languageVersionSettings) + ) Opcodes.ACC_SYNTHETIC else 0) + +private fun IrField.isPrivateCompanionFieldInInterface(languageVersionSettings: LanguageVersionSettings): Boolean = + origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE && + languageVersionSettings.supportsFeature(LanguageFeature.ProperVisibilityForCompanionObjectInstanceField) && + parentAsClass.isJvmInterface && + DescriptorVisibilities.isPrivate(parentAsClass.companionObject()!!.visibility) + +private val IrDeclarationOrigin.flags: Int + get() = (if (isSynthetic) Opcodes.ACC_SYNTHETIC else 0) or + (if (this == IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) Opcodes.ACC_ENUM else 0) + +private val Modality.flags: Int + get() = when (this) { + Modality.ABSTRACT, Modality.SEALED -> Opcodes.ACC_ABSTRACT + Modality.FINAL -> Opcodes.ACC_FINAL + Modality.OPEN -> 0 + else -> throw AssertionError("Unsupported modality $this") + } + +private val DescriptorVisibility.flags: Int + get() = DescriptorAsmUtil.getVisibilityAccessFlag(this) ?: throw AssertionError("Unsupported visibility $this") diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt index 4f33b251730..29d9c49f08e 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.org.objectweb.asm.Type @@ -166,37 +167,40 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin + context.irBuiltIns.anyNType ) superTypes = listOf(suspendLambda.defaultType, functionNType) - val usedParams = ArrayList(function.explicitParameters.size) + val usedParams = mutableSetOf() // marking the parameters referenced in the function function.acceptChildrenVoid( object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) = - if (element is IrDeclarationReference && element.symbol is IrValueParameterSymbol && element.symbol.owner in function.explicitParameters) - usedParams += element.symbol.owner - else - element.acceptChildrenVoid(this) + override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this) + + override fun visitGetValue(expression: IrGetValue) { + if (expression.symbol is IrValueParameterSymbol && expression.symbol.owner in function.explicitParameters) { + usedParams += expression.symbol.owner + } + } }, ) addField(COROUTINE_LABEL_FIELD_NAME, context.irBuiltIns.intType, JavaDescriptorVisibilities.PACKAGE_VISIBILITY) val varsCountByType = HashMap() - val parametersFields = function.explicitParameters.filter { it in usedParams }.map { - addField { + val parametersFields = function.explicitParameters.map { + val field = if (it in usedParams) addField { val normalizedType = context.typeMapper.mapType(it.type).normalize() val index = varsCountByType[normalizedType]?.plus(1) ?: 0 varsCountByType[normalizedType] = index // Rename `$this` to avoid being caught by inlineCodegenUtils.isCapturedFieldName() name = Name.identifier("${normalizedType.descriptor[0]}$$index") - type = it.type + type = if (normalizedType == AsmTypes.OBJECT_TYPE) context.irBuiltIns.anyNType else it.type origin = LocalDeclarationsLowering.DECLARATION_ORIGIN_FIELD_FOR_CAPTURED_VALUE isFinal = false visibility = if (it.index < 0) DescriptorVisibilities.PRIVATE else JavaDescriptorVisibilities.PACKAGE_VISIBILITY - } + } else null + ParameterInfo(field, it.type, it.name) } - context.continuationClassesVarsCountByType[this] = varsCountByType + context.continuationClassesVarsCountByType[attributeOwnerId] = varsCountByType val constructor = addPrimaryConstructorForLambda(suspendLambda, arity) val invokeToOverride = functionNClass.functions.single { it.owner.valueParameters.size == arity + 1 && it.owner.name.asString() == "invoke" @@ -218,26 +222,34 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin context.suspendLambdaToOriginalFunctionMap[attributeOwnerId as IrFunctionReference] = function } - private fun IrClass.addInvokeSuspendForLambda(irFunction: IrFunction, suspendLambda: IrClass, fields: List): IrSimpleFunction { + private fun IrClass.addInvokeSuspendForLambda( + irFunction: IrFunction, + suspendLambda: IrClass, + fields: List + ): IrSimpleFunction { val superMethod = suspendLambda.functions.single { it.name.asString() == INVOKE_SUSPEND_METHOD_NAME && it.valueParameters.size == 1 && it.valueParameters[0].type.isKotlinResult() } return addFunctionOverride(superMethod, irFunction.startOffset, irFunction.endOffset).apply { - val localVals: List = fields.mapIndexed { index, field -> - buildVariable( - parent = this, - startOffset = UNDEFINED_OFFSET, - endOffset = UNDEFINED_OFFSET, - origin = IrDeclarationOrigin.DEFINED, - name = field.name, - type = field.type - ).apply { - val receiver = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, dispatchReceiverParameter!!.symbol) - val initializerBlock = IrBlockImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, field.type) - initializerBlock.statements += IrGetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, field.symbol, field.type, receiver) - initializer = initializerBlock - } + val localVals: List = fields.mapIndexed { index, param -> + if (param.isUsed) { + buildVariable( + parent = this, + startOffset = UNDEFINED_OFFSET, + endOffset = UNDEFINED_OFFSET, + origin = IrDeclarationOrigin.DEFINED, + name = param.name, + type = param.type + ).apply { + val receiver = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, dispatchReceiverParameter!!.symbol) + val initializerBlock = IrBlockImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type) + initializerBlock.statements += IrGetFieldImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, param.field!!.symbol, type, receiver + ) + initializer = initializerBlock + } + } else null } body = irFunction.moveBodyTo(this, mapOf())?.let { body -> @@ -246,10 +258,11 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin val parameter = (expression.symbol.owner as? IrValueParameter)?.takeIf { it.parent == irFunction } ?: return expression val lvar = localVals[parameter.index + if (irFunction.extensionReceiverParameter != null) 1 else 0] + ?: return expression return IrGetValueImpl(expression.startOffset, expression.endOffset, lvar.symbol) } }, null) - context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, localVals + body.statements) + context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, localVals.filterNotNull() + body.statements) } } } @@ -292,7 +305,7 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin constructor: IrFunction, invokeSuspend: IrSimpleFunction, invokeToOverride: IrSimpleFunctionSymbol, - fieldsForUnbound: List + fieldsForUnbound: List ) = addFunctionOverride(invokeToOverride.owner) { function -> +irReturn(callInvokeSuspend(invokeSuspend, cloneLambda(function, constructor, fieldsForUnbound))) } @@ -300,7 +313,7 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin private fun IrClass.addCreate( constructor: IrFunction, createToOverride: IrSimpleFunctionSymbol, - fieldsForUnbound: List + fieldsForUnbound: List ) = addFunctionOverride(createToOverride.owner) { function -> +irReturn(cloneLambda(function, constructor, fieldsForUnbound)) } @@ -308,7 +321,7 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin private fun IrBlockBodyBuilder.cloneLambda( scope: IrFunction, constructor: IrFunction, - fieldsForUnbound: List + fieldsForUnbound: List ): IrExpression { val constructorCall = irCall(constructor).also { for (typeParameter in constructor.parentAsClass.typeParameters) { @@ -316,12 +329,14 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin } it.putValueArgument(0, irGet(scope.valueParameters.last())) } - if (fieldsForUnbound.isEmpty()) { + if (fieldsForUnbound.none { it.isUsed }) { return constructorCall } val result = irTemporary(constructorCall, "result") for ((index, field) in fieldsForUnbound.withIndex()) { - +irSetField(irGet(result), field, irGet(scope.valueParameters[index])) + if (field.isUsed) { + +irSetField(irGet(result), field.field!!, irGet(scope.valueParameters[index])) + } } return irGet(result) } @@ -348,3 +363,7 @@ private class SuspendLambdaLowering(context: JvmBackendContext) : SuspendLowerin } } } + +private data class ParameterInfo(val field: IrField?, val type: IrType, val name: Name) { + val isUsed = field != null +} diff --git a/compiler/testData/checkLocalVariablesTable/parametersInSuspendLambda/parameters.kt b/compiler/testData/checkLocalVariablesTable/parametersInSuspendLambda/parameters.kt index 964126eeb98..d72e004d206 100644 --- a/compiler/testData/checkLocalVariablesTable/parametersInSuspendLambda/parameters.kt +++ b/compiler/testData/checkLocalVariablesTable/parametersInSuspendLambda/parameters.kt @@ -28,8 +28,8 @@ suspend fun foo(data: Data, body: suspend Long.(String, Data, Int) -> Unit) { // JVM_IR_TEMPLATES // VARIABLE : NAME=$dstr$x$_u24__u24$z TYPE=LData; INDEX=* // VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=* +// VARIABLE : NAME=$this$foo TYPE=J INDEX=* // VARIABLE : NAME=i TYPE=I INDEX=* -// VARIABLE : NAME=p$ TYPE=J INDEX=* // VARIABLE : NAME=str TYPE=Ljava/lang/String; INDEX=* // VARIABLE : NAME=this TYPE=LParametersKt$test$2; INDEX=* // VARIABLE : NAME=x TYPE=Ljava/lang/String; INDEX=* diff --git a/compiler/testData/codegen/box/coroutines/bridges/lambdaWithLongReceiver_ir.txt b/compiler/testData/codegen/box/coroutines/bridges/lambdaWithLongReceiver_ir.txt index 227e622dfa9..15e55a96663 100644 --- a/compiler/testData/codegen/box/coroutines/bridges/lambdaWithLongReceiver_ir.txt +++ b/compiler/testData/codegen/box/coroutines/bridges/lambdaWithLongReceiver_ir.txt @@ -3,8 +3,8 @@ final class LambdaWithLongReceiverKt$box$1$1 { // source: 'lambdaWithLongReceiver.kt' enclosing method LambdaWithLongReceiverKt$box$1.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; + private synthetic field J$0: long field label: int - private synthetic field p$: long inner (anonymous) class LambdaWithLongReceiverKt$box$1 inner (anonymous) class LambdaWithLongReceiverKt$box$1$1 method (p0: kotlin.coroutines.Continuation): void diff --git a/compiler/testData/codegen/box/coroutines/bridges/lambdaWithMultipleParameters_ir.txt b/compiler/testData/codegen/box/coroutines/bridges/lambdaWithMultipleParameters_ir.txt index f76c0eba8a5..4d64ec4f837 100644 --- a/compiler/testData/codegen/box/coroutines/bridges/lambdaWithMultipleParameters_ir.txt +++ b/compiler/testData/codegen/box/coroutines/bridges/lambdaWithMultipleParameters_ir.txt @@ -3,12 +3,12 @@ final class LambdaWithMultipleParametersKt$box$1$1 { // source: 'lambdaWithMultipleParameters.kt' enclosing method LambdaWithMultipleParametersKt$box$1.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; - synthetic field a: java.lang.String - synthetic field b: long - synthetic field c: long - synthetic field d: long - synthetic field e: long - synthetic field f: long + synthetic field J$0: long + synthetic field J$1: long + synthetic field J$2: long + synthetic field J$3: long + synthetic field J$4: long + synthetic field L$0: java.lang.Object field label: int inner (anonymous) class LambdaWithMultipleParametersKt$box$1 inner (anonymous) class LambdaWithMultipleParametersKt$box$1$1 diff --git a/compiler/testData/codegen/bytecodeListing/coroutines/coroutineFields_ir.txt b/compiler/testData/codegen/bytecodeListing/coroutines/coroutineFields_ir.txt index ccdab5c96ac..e6c1debac5e 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutines/coroutineFields_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutines/coroutineFields_ir.txt @@ -44,10 +44,9 @@ final class CoroutineFieldsKt$box$1 { enclosing method CoroutineFieldsKt.box()Ljava/lang/String; synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef field J$0: long - field L$0: java.lang.Object + private synthetic field L$0: java.lang.Object field L$1: java.lang.Object field label: int - private synthetic field p$: Controller inner (anonymous) class CoroutineFieldsKt$box$1 method (p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.Continuation): void public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): kotlin.coroutines.Continuation diff --git a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/booleanParameter_ir.txt b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/booleanParameter_ir.txt index f2782e96fed..f51bbbbc24f 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/booleanParameter_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/booleanParameter_ir.txt @@ -20,7 +20,7 @@ final class BooleanParameterKt$box$1 { final class BooleanParameterKt$box$lambda$1 { // source: 'booleanParameter.kt' enclosing method BooleanParameterKt.box()Ljava/lang/String; - synthetic field it: boolean + synthetic field Z$0: boolean field label: int inner (anonymous) class BooleanParameterKt$box$lambda$1 method (p0: kotlin.coroutines.Continuation): void diff --git a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/component1_ir.txt b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/component1_ir.txt index c9795b136d8..3f643f55fc7 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/component1_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/component1_ir.txt @@ -3,8 +3,8 @@ final class Component1Kt$test$1 { // source: 'component1.kt' enclosing method Component1Kt.test()V + private synthetic field L$0: java.lang.Object field label: int - private synthetic field p$: Foo inner (anonymous) class Component1Kt$test$1 method (p0: kotlin.coroutines.Continuation): void public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): kotlin.coroutines.Continuation diff --git a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/field_ir.txt b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/field_ir.txt index b3850efd1d4..8d7f6c0f016 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutines/spilling/field_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutines/spilling/field_ir.txt @@ -3,8 +3,8 @@ final class FieldKt$test$1 { // source: 'field.kt' enclosing method FieldKt.test()V + private synthetic field L$0: java.lang.Object field label: int - private synthetic field p$: Foo inner (anonymous) class FieldKt$test$1 method (p0: kotlin.coroutines.Continuation): void public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): kotlin.coroutines.Continuation