diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt index daf11aee8b2..55746e2eea8 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.BodyAndScriptBodyLoweringPass import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString import org.jetbrains.kotlin.backend.common.ir.* +import org.jetbrains.kotlin.backend.common.lower.inline.isInlineParameter import org.jetbrains.kotlin.backend.common.runOnFilePostfix import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality @@ -79,6 +80,7 @@ class LocalDeclarationsLowering( val localNameProvider: LocalNameProvider = LocalNameProvider.DEFAULT, val visibilityPolicy: VisibilityPolicy = VisibilityPolicy.DEFAULT, val suggestUniqueNames: Boolean = true, // When `true` appends a `-#index` suffix to lifted declaration names + val forceFieldsForInlineCaptures: Boolean = false // See `LocalClassContext` ) : BodyAndScriptBodyLoweringPass { @@ -163,7 +165,11 @@ class LocalDeclarationsLowering( override lateinit var transformedDeclaration: IrConstructor } - private class LocalClassContext(val declaration: IrClass, val inInlineFunctionScope: Boolean) : LocalContext() { + private inner class LocalClassContext( + val declaration: IrClass, + val inInlineFunctionScope: Boolean, + val constructorContext: LocalContext? + ) : LocalContext() { lateinit var closure: Closure // NOTE: This map is iterated over in `rewriteClassMembers` and we're relying on @@ -171,6 +177,19 @@ class LocalDeclarationsLowering( val capturedValueToField: MutableMap> = mutableMapOf() override fun irGet(startOffset: Int, endOffset: Int, valueDeclaration: IrValueDeclaration): IrExpression? { + // On the JVM backend, `AnonymousObjectTransformer` in the bytecode inliner uses field assignment + // instructions to find captured parameters. Most of the time this is unimportant, as captured + // parameters are effectively indistinguishable from normal ones; the exception is ones that can + // take an inline lambda value, as the parameter needs to be replaced with the lambda's capture. + // Thus we cannot erase the field - the inliner won't handle the inline lambda without it. + // (Most of the time this is inconsequential - if the object really is instantiated with an inline + // lambda, the field will be erased completely regardless of whether it's used.) + if (!forceFieldsForInlineCaptures || !valueDeclaration.isInlineDeclaration()) { + // We're in the initializer scope, which will be moved to a primary constructor later. + // Thus we can directly use that constructor's context and read from a parameter instead of a field. + constructorContext?.irGet(startOffset, endOffset, valueDeclaration)?.let { return it } + } + val field = capturedValueToField[valueDeclaration]?.value ?: return null val receiver = declaration.thisReceiver!! @@ -179,13 +198,16 @@ class LocalDeclarationsLowering( receiver = IrGetValueImpl(startOffset, endOffset, receiver.type, receiver.symbol) ) } + + private fun IrValueDeclaration.isInlineDeclaration() = + this is IrValueParameter && parent.let { it is IrFunction && it.isInline } && isInlineParameter() } - private class LocalClassMemberContext(val member: IrFunction, val classContext: LocalClassContext) : LocalContext() { + private class LocalClassMemberContext(val member: IrDeclaration, val classContext: LocalClassContext) : LocalContext() { override fun irGet(startOffset: Int, endOffset: Int, valueDeclaration: IrValueDeclaration): IrExpression? { val field = classContext.capturedValueToField[valueDeclaration]?.value ?: return null - - val receiver = member.dispatchReceiverParameter + // This lowering does not process accesses to outer `this`. + val receiver = (if (member is IrFunction) member.dispatchReceiverParameter else classContext.declaration.thisReceiver) ?: error("No dispatch receiver parameter for ${member.render()}") return IrGetFieldImpl( startOffset, endOffset, field.symbol, field.type, @@ -258,27 +280,26 @@ class LocalDeclarationsLowering( // Both accessors extracted as closures. declaration.delegate.transformStatement(this) - override fun visitClass(declaration: IrClass) = if (declaration in localClasses) { - localClasses[declaration]!!.declaration - } else { - super.visitClass(declaration) - } + override fun visitClass(declaration: IrClass) = + localClasses[declaration]?.declaration + ?: visitMember(declaration) + ?: super.visitClass(declaration) - override fun visitFunction(declaration: IrFunction): IrStatement { - return if (declaration in localFunctions) { + override fun visitFunction(declaration: IrFunction): IrStatement = + if (declaration in localFunctions) { // Replace local function definition with an empty composite. IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.irBuiltIns.unitType) } else { - if (localContext is LocalClassContext && declaration.parent == localContext.declaration) { - declaration.apply { - val classMemberLocalContext = LocalClassMemberContext(declaration, localContext) - transformChildrenVoid(FunctionBodiesRewriter(classMemberLocalContext)) - } - } else { - super.visitFunction(declaration) - } + visitMember(declaration) ?: super.visitFunction(declaration) + } + + private fun visitMember(declaration: IrDeclaration): IrStatement? = + if (localContext is LocalClassContext && declaration.parent == localContext.declaration) { + val classMemberLocalContext = LocalClassMemberContext(declaration, localContext) + declaration.apply { transformChildrenVoid(FunctionBodiesRewriter(classMemberLocalContext)) } + } else { + null } - } override fun visitConstructor(declaration: IrConstructor): IrStatement { // Body is transformed separately. See loop over constructors in rewriteDeclarations(). @@ -469,6 +490,8 @@ class LocalDeclarationsLowering( if (isScriptMode && constructors.isEmpty()) return + // NOTE: if running before InitializersLowering, we can instead look for constructors that have + // IrInstanceInitializerCall. However, Native runs these two lowerings in opposite order. val constructorsCallingSuper = constructors .asSequence() .map { localClassConstructors[it]!! } @@ -953,7 +976,18 @@ class LocalDeclarationsLowering( // LDL doesn't work on the enums because local enums are not allowed, so skipping them in scripts too if (isScriptMode && declaration.kind == ClassKind.ENUM_CLASS) return - localClasses[declaration] = LocalClassContext(declaration, data.inInlineFunctionScope) + // If there are many non-delegating constructors, each copy of the initializer requires different remapping: + // class C { + // constructor() {} + // constructor(x: Int) {} + // val x = y // which constructor's parameter? + // } + // TODO: this should ideally run after initializers are added to constructors, but that'd place + // other restrictions on IR (e.g. after the initializers are moved you can no longer create fields + // with initializers) which makes that hard to implement. + val constructorContext = declaration.constructors.mapNotNull { localClassConstructors[it] } + .singleOrNull { it.declaration.callsSuper(context.irBuiltIns) } + localClasses[declaration] = LocalClassContext(declaration, data.inInlineFunctionScope, constructorContext) } private val Data.inInlineFunctionScope: Boolean diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index e43d1e54c46..61c4f26ec9f 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -141,7 +141,8 @@ internal val localDeclarationsPhase = makeIrFilePhase( private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility = if (inInlineFunctionScope) DescriptorVisibilities.PUBLIC else JavaDescriptorVisibilities.PACKAGE_VISIBILITY - } + }, + forceFieldsForInlineCaptures = true ) }, name = "JvmLocalDeclarations", diff --git a/compiler/testData/codegen/bytecodeListing/annotations/localClassWithCapturedParams_ir.txt b/compiler/testData/codegen/bytecodeListing/annotations/localClassWithCapturedParams_ir.txt new file mode 100644 index 00000000000..8eeee8fa6a4 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/annotations/localClassWithCapturedParams_ir.txt @@ -0,0 +1,25 @@ +@kotlin.Metadata +public final class LocalClassWithCapturedParamsKt$localCaptured$A { + // source: 'localClassWithCapturedParams.kt' + enclosing method LocalClassWithCapturedParamsKt.localCaptured()Ljava/lang/Object; + private final field x: int + private final @org.jetbrains.annotations.NotNull field z: java.lang.String + inner (local) class LocalClassWithCapturedParamsKt$localCaptured$A A + public method (p0: int, @Simple(value="K") @org.jetbrains.annotations.NotNull p1: java.lang.String): void + public final method getX(): int + public final @org.jetbrains.annotations.NotNull method getZ(): java.lang.String +} + +@kotlin.Metadata +public final class LocalClassWithCapturedParamsKt { + // source: 'localClassWithCapturedParams.kt' + inner (local) class LocalClassWithCapturedParamsKt$localCaptured$A A + public final static @org.jetbrains.annotations.NotNull method localCaptured(): java.lang.Object +} + +@java.lang.annotation.Retention(value=RUNTIME) +@kotlin.Metadata +public annotation class Simple { + // source: 'localClassWithCapturedParams.kt' + public abstract method value(): java.lang.String +} diff --git a/compiler/testData/codegen/bytecodeText/fieldsForCapturedValues/constructorOnly.kt b/compiler/testData/codegen/bytecodeText/fieldsForCapturedValues/constructorOnly.kt index 30a464ab4de..eb32f697ef9 100644 --- a/compiler/testData/codegen/bytecodeText/fieldsForCapturedValues/constructorOnly.kt +++ b/compiler/testData/codegen/bytecodeText/fieldsForCapturedValues/constructorOnly.kt @@ -2,8 +2,8 @@ open class Base(parameter: String) fun foo(captured: String) { object : Base(captured) { - // val x = captured - // init { println(captured) } + val x = captured + init { println(captured) } } }