diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt index 95bf643a0e0..56f6eefd82e 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt @@ -13,8 +13,9 @@ import org.jetbrains.kotlin.backend.common.lower.loops.isInductionVariable import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin -import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder +import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrStatement @@ -109,42 +110,20 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass AsmUtil.isPrimitive(context.typeMapper.mapType(this)) override fun lower(irFile: IrFile) { - irFile.transformChildren(Transformer(), null) + irFile.transformChildren(Transformer(irFile.findInlineCallSites(context)), null) } - inner class Transformer : IrElementTransformer { - + private inner class Transformer(private val inlineScopeResolver: IrInlineScopeResolver) : IrElementTransformer { private val dontTouchTemporaryVals = HashSet() - // Thread the current class through the transformations in order to replace - // final default accessor calls with direct backing field access when - // possible. - override fun visitClass(declaration: IrClass, data: IrClass?): IrStatement { - declaration.transformChildren(this, declaration) - return declaration - } + override fun visitDeclaration(declaration: IrDeclarationBase, data: IrDeclaration?): IrStatement = + super.visitDeclaration(declaration, declaration) - // For some functions, we clear the current class field since the code could end up - // in another class then the one it is nested under in the IR. - // TODO: replace this with the code from SyntheticAccessorLowering that returns the current class - // or package accounting for all inline functions and lambdas. - override fun visitFunction(declaration: IrFunction, data: IrClass?): IrStatement { - val codeMightBeGeneratedInDifferentClass = declaration.isSuspend || - declaration.isInline || - declaration.origin == JvmLoweredDeclarationOrigin.INLINE_LAMBDA - declaration.transformChildren(this, data.takeUnless { codeMightBeGeneratedInDifferentClass }) - return declaration - } - - override fun visitCall(expression: IrCall, data: IrClass?): IrExpression { + override fun visitCall(expression: IrCall, data: IrDeclaration?): IrExpression { expression.transformChildren(this, data) if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) { - if (data == null) return expression - val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression - val property = simpleFunction.correspondingPropertySymbol?.owner ?: return expression - if (property.isLateinit) return expression - return optimizePropertyAccess(expression, simpleFunction, property, data) + return optimizePropertyAccess(expression, data) } if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) { @@ -212,39 +191,33 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass } } - private fun optimizePropertyAccess( - expression: IrCall, - accessor: IrSimpleFunction, - property: IrProperty, - currentClass: IrClass - ): IrExpression { - if (accessor.parentAsClass == currentClass && - property.backingField?.parentAsClass == currentClass && - accessor.modality == Modality.FINAL && - !accessor.isExternal - ) { - val backingField = property.backingField!! - val receiver = expression.dispatchReceiver - return context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset).irBlock(expression) { - if (backingField.isStatic && receiver != null && receiver !is IrGetValue) { - // If the field is static, evaluate the receiver for potential side effects. - +receiver.coerceToUnit(context.irBuiltIns, this@JvmOptimizationLowering.context.typeSystem) - } - if (accessor.valueParameters.isNotEmpty()) { - +irSetField( - receiver.takeUnless { backingField.isStatic }, - backingField, - expression.getValueArgument(expression.valueArgumentsCount - 1)!! - ) - } else { - +irGetField(receiver.takeUnless { backingField.isStatic }, backingField) - } + private fun optimizePropertyAccess(expression: IrCall, data: IrDeclaration?): IrExpression { + val accessor = expression.symbol.owner as? IrSimpleFunction ?: return expression + if (accessor.modality != Modality.FINAL || accessor.isExternal) return expression + val property = accessor.correspondingPropertySymbol?.owner ?: return expression + if (property.isLateinit) return expression + val backingField = property.backingField ?: return expression + val scope = data?.let(inlineScopeResolver::findContainer) ?: return expression + if (scope != accessor.parent || scope != backingField.parent) return expression + val receiver = expression.dispatchReceiver + return context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset).irBlock(expression) { + if (backingField.isStatic && receiver != null && receiver !is IrGetValue) { + // If the field is static, evaluate the receiver for potential side effects. + +receiver.coerceToUnit(context.irBuiltIns, this@JvmOptimizationLowering.context.typeSystem) + } + if (accessor.valueParameters.isNotEmpty()) { + +irSetField( + receiver.takeUnless { backingField.isStatic }, + backingField, + expression.getValueArgument(expression.valueArgumentsCount - 1)!! + ) + } else { + +irGetField(receiver.takeUnless { backingField.isStatic }, backingField) } } - return expression } - override fun visitWhen(expression: IrWhen, data: IrClass?): IrExpression { + override fun visitWhen(expression: IrWhen, data: IrDeclaration?): IrExpression { val isCompilerGenerated = expression.origin == null expression.transformChildren(this, data) // Remove all branches with constant false condition. @@ -390,13 +363,13 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass } } - override fun visitBlockBody(body: IrBlockBody, data: IrClass?): IrBody { + override fun visitBlockBody(body: IrBlockBody, data: IrDeclaration?): IrBody { body.transformChildren(this, data) removeUnnecessaryTemporaryVariables(body.statements) return body } - override fun visitContainerExpression(expression: IrContainerExpression, data: IrClass?): IrExpression { + override fun visitContainerExpression(expression: IrContainerExpression, data: IrDeclaration?): IrExpression { val safeCall = parseSafeCall(expression) if (safeCall != null) { // Don't optimize out temporary values for safe calls (yet), so that safe call-based equality checks can be optimized. @@ -450,6 +423,7 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass isConst = false, isLateinit = false ) newLoopVariable.initializer = inductionVariable.initializer + newLoopVariable.parent = inductionVariable.parent loopInitialization.statements[inductionVariableIndex] = newLoopVariable loopVariableContainer.statements.removeAt(loopVariableIndex) @@ -519,7 +493,7 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass } } - override fun visitGetValue(expression: IrGetValue, data: IrClass?): IrExpression { + override fun visitGetValue(expression: IrGetValue, data: IrDeclaration?): IrExpression { // Replace IrGetValue of an immutable temporary variable with a constant // initializer with the constant initializer. val variable = expression.symbol.owner diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt index 20e99229d69..937d48e4ca1 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt @@ -11,17 +11,16 @@ import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString import org.jetbrains.kotlin.backend.common.ir.* import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin -import org.jetbrains.kotlin.backend.jvm.codegen.fileParent import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface import org.jetbrains.kotlin.backend.jvm.hasMangledParameters import org.jetbrains.kotlin.backend.jvm.intrinsics.receiverAndArgs -import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator +import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver +import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites import org.jetbrains.kotlin.backend.jvm.ir.isAssertionsDisabledField import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter @@ -36,54 +35,13 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities import org.jetbrains.kotlin.load.java.JvmAbi -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.org.objectweb.asm.Opcodes -internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass { - data class LambdaCallSite(val scope: IrDeclaration, val crossinline: Boolean) - - private val pendingAccessorsToAdd = mutableListOf() - private val inlineLambdaToCallSite = mutableMapOf() - private val inlineFunctionToCallSites = mutableMapOf>() - +internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass { override fun lower(irFile: IrFile) { - irFile.accept(object : IrInlineReferenceLocator(context) { - override fun visitInlineLambda( - argument: IrFunctionReference, - callee: IrFunction, - parameter: IrValueParameter, - scope: IrDeclaration - ) { - // suspendCoroutine and suspendCoroutineUninterceptedOrReturn accept crossinline lambdas to disallow non-local returns, - // but these lambdas are effectively inline - inlineLambdaToCallSite[argument.symbol.owner] = - LambdaCallSite(scope, parameter.isCrossinline && !callee.isCoroutineIntrinsic()) - } - - override fun visitSimpleFunction(declaration: IrSimpleFunction, data: IrDeclaration?) { - if (declaration.isPrivateInline) { - inlineFunctionToCallSites.putIfAbsent(declaration, mutableSetOf()) - } - super.visitSimpleFunction(declaration, data) - } - - override fun visitCall(expression: IrCall, data: IrDeclaration?) { - val callee = expression.symbol.owner - if (callee.isPrivateInline && callee.fileParent == irFile && data != null) { - (inlineFunctionToCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data) - } - super.visitCall(expression, data) - } - - private inline val IrSimpleFunction.isPrivateInline - get() = isInline && DescriptorVisibilities.isPrivate(visibility) - }, null) - - irFile.transformChildrenVoid(this) - inlineLambdaToCallSite.clear() - inlineFunctionToCallSites.clear() - + val pendingAccessorsToAdd = mutableListOf() + irFile.transformChildrenVoid(SyntheticAccessorTransformer(context, irFile.findInlineCallSites(context), pendingAccessorsToAdd)) for (accessor in pendingAccessorsToAdd) { assert(accessor.fileOrNull == irFile) { "SyntheticAccessorLowering should not attempt to modify other files!\n" + @@ -92,9 +50,14 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle } (accessor.parent as IrDeclarationContainer).declarations.add(accessor) } - pendingAccessorsToAdd.clear() } +} +private class SyntheticAccessorTransformer( + val context: JvmBackendContext, + val inlineScopeResolver: IrInlineScopeResolver, + val pendingAccessorsToAdd: MutableList +) : IrElementTransformerVoidWithContext() { private data class FieldKey(val fieldSymbol: IrFieldSymbol, val parent: IrDeclarationParent, val superQualifierSymbol: IrClassSymbol?) private data class FunctionKey( @@ -259,7 +222,7 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle // We have a protected member. // It is accessible from a synthetic proxy class (created by LambdaMetafactory) // if it belongs to the current class. - return getScopeClassOrPackage() == owner.parentAsClass + return inlineScopeResolver.findContainer(currentScope!!.irElement) == owner.parentAsClass } override fun visitGetField(expression: IrGetField): IrExpression { @@ -762,7 +725,7 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle } val ownerClass = declaration.parent as? IrClass ?: return true // locals are always accessible - val scopeClassOrPackage = getScopeClassOrPackage() ?: return false + val scopeClassOrPackage = inlineScopeResolver.findContainer(currentScope!!.irElement) ?: return false val samePackage = ownerClass.getPackageFragment()?.fqName == scopeClassOrPackage.getPackageFragment()?.fqName return when { jvmVisibility == 0 /* package only */ -> samePackage @@ -776,84 +739,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle (thisObjReference == null || thisObjReference.owner.isSubclassOf(scopeClassOrPackage)) } } - - private fun getScopeClassOrPackage(): IrDeclarationContainer? = - getScopeClassOrPackage(currentScope?.irElement, approximateToPackage = false) - - // Get the class from which all accesses in the current scope will be done after bytecode generation. - // If the current scope is a crossinline lambda, this is not possible, as the lambda maybe inlined - // into some other class; in that case, get at least the package. - private tailrec fun getScopeClassOrPackage(context: IrElement?, approximateToPackage: Boolean): IrDeclarationContainer? { - val callSite = inlineLambdaToCallSite[context] - return when { - // Crossinline lambdas can be inlined into some other class in the same package. However, - // classes within crossinline lambdas should not be regenerated, so if we've already found - // a class *before* reaching this lambda, it's valid: - // class C { - // fun f() {} - // fun g() = inlineFunctionWithCrossinlineArgument { - // f() // this call is done in some unknown class within C's package - // object { val x = f() } // this call is done in C$g$1$1 - // } - // } - callSite != null -> getScopeClassOrPackage(callSite.scope, approximateToPackage || callSite.crossinline) - // Inline functions can be inlined into anywhere. Not even private inline functions are safe: - // class C { - // fun f() {} - // private inline fun g1() = f() // `f` is called from C? - // fun g2() = { g1() } // ...or from C$g2$1 in the same package? - // inline fun g3() = g1() // ...or from some other package that calls g3? - // } - // However, for private ones we at least know where they're called, so just like inline lambdas, - // we can navigate there. - // - // TODO: this has some weird effects for inline functions in local classes, e.g. they - // access the capture fields (package-private) through accessors; this may or may not - // be necessary - local types should in theory not be usable outside the current file. - context is IrFunction && context.isInline -> { - val callSites = inlineFunctionToCallSites[context] ?: return null - when { - callSites.isEmpty() -> getScopeClassOrPackage(context.parent, approximateToPackage) - callSites.size == 1 -> getScopeClassOrPackage(callSites.single(), approximateToPackage) - else -> { - // TODO: cache the results - @Suppress("NON_TAIL_RECURSIVE_CALL") - val results = callSites.map { getScopeClassOrPackage(it, approximateToPackage = false) ?: return null } - // If all call sites are within a single class, use it. Otherwise, all scopes must be within - // the current file's package. - val single = results.first().takeIf { results.all { other -> it === other } } - getScopeClassOrPackage(single ?: context.parent, approximateToPackage || single == null) - } - } - } - // TODO: if this class is an object local to an inline function, it could be regenerated, - // so the scope depends on the declaration accessed (see KT-48508): - // class C { - // fun f1() - // inline fun inlineFun() = object { - // fun f2() {} - // fun g1() { - // f1() // this access can be anywhere - // f2() // can pretend this access is from C$foo$1 - // } - // } - // } - // Further complicating things, the accessor for `f1` cannot be in `C$inlineFun$1`, as otherwise - // the accessor itself will be regenerated (and thus not work) at `inlineFun` call sites. - context is IrClass && !approximateToPackage -> context - // Inline lambdas have already been moved out to the containing class, but we still need to check - // the containing function (again, see above), so navigate there instead. - context is IrDeclaration -> getScopeClassOrPackage(context.parent, approximateToPackage) - // The only non-declaration parent should be the package. - else -> context as? IrPackageFragment - } - } } -private fun IrFunction.isCoroutineIntrinsic(): Boolean = - (name.asString() == "suspendCoroutine" && getPackageFragment()?.fqName == FqName("kotlin.coroutines")) || - (name.asString() == "suspendCoroutineUninterceptedOrReturn" && getPackageFragment()?.fqName == FqName("kotlin.coroutines.intrinsics")) - private fun IrClass.syntheticAccessorToSuperSuffix(): String = // TODO: change this to `fqNameUnsafe.asString().replace(".", "_")` as soon as we're ready to break compatibility with pre-KT-21178 code name.asString().hashCode().toString() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrInlineReferenceLocator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrInlineReferenceLocator.kt index 94265a45240..bd2c88feba4 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrInlineReferenceLocator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrInlineReferenceLocator.kt @@ -8,11 +8,15 @@ package org.jetbrains.kotlin.backend.jvm.ir import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.codegen.isInlineFunctionCall import org.jetbrains.kotlin.backend.jvm.codegen.unwrapInlineLambda +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionReference +import org.jetbrains.kotlin.ir.util.getPackageFragment import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.name.FqName abstract class IrInlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitor { override fun visitElement(element: IrElement, data: IrDeclaration?) = @@ -32,9 +36,123 @@ abstract class IrInlineReferenceLocator(private val context: JvmBackendContext) abstract fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) } +class IrInlineScopeResolver(context: JvmBackendContext) : IrInlineReferenceLocator(context) { + private class CallSite(val parent: IrElement?, val approximateToPackage: Boolean) + + private val inlineCallSites = mutableMapOf() + private val inlineFunctionCallSites = mutableMapOf>() + + override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) { + // suspendCoroutine and suspendCoroutineUninterceptedOrReturn accept crossinline lambdas to disallow non-local returns, + // but these lambdas are effectively inline + inlineCallSites[argument.symbol.owner] = CallSite(scope, parameter.isCrossinline && !callee.isCoroutineIntrinsic()) + } + + override fun visitSimpleFunction(declaration: IrSimpleFunction, data: IrDeclaration?) { + if (declaration.isPrivateInline) { + inlineFunctionCallSites.putIfAbsent(declaration, mutableSetOf()) + } + super.visitSimpleFunction(declaration, data) + } + + override fun visitCall(expression: IrCall, data: IrDeclaration?) { + val callee = expression.symbol.owner + if (callee.isPrivateInline && data != null) { + (inlineFunctionCallSites.getOrPut(callee) { mutableSetOf() } as MutableSet).add(data) + } + super.visitCall(expression, data) + } + + private inline val IrSimpleFunction.isPrivateInline + get() = isInline && DescriptorVisibilities.isPrivate(visibility) + + private fun IrFunction.isCoroutineIntrinsic(): Boolean = + (name.asString() == "suspendCoroutine" && getPackageFragment()?.fqName == FqName("kotlin.coroutines")) || + (name.asString() == "suspendCoroutineUninterceptedOrReturn" && getPackageFragment()?.fqName == FqName("kotlin.coroutines.intrinsics")) + + fun findContainer(scope: IrElement): IrDeclarationContainer? = + findContainer(scope, approximateToPackage = false) + + // Get the class from which all accesses in the current scope will be done after bytecode generation. + // If the current scope is a crossinline lambda, this is not possible, as the lambda maybe inlined + // into some other class; in that case, get at least the package. + private tailrec fun findContainer(context: IrElement?, approximateToPackage: Boolean): IrDeclarationContainer? { + val callSite = inlineCallSites[context] + return when { + // Crossinline lambdas can be inlined into some other class in the same package. However, + // classes within crossinline lambdas should not be regenerated, so if we've already found + // a class *before* reaching this lambda, it's valid: + // class C { + // fun f() {} + // fun g() = inlineFunctionWithCrossinlineArgument { + // f() // this call is done in some unknown class within C's package + // object { val x = f() } // this call is done in C$g$1$1 + // } + // } + callSite != null -> findContainer(callSite.parent, approximateToPackage || callSite.approximateToPackage) + // Inline functions can be inlined into anywhere. Not even private inline functions are safe: + // class C { + // fun f() {} + // private inline fun g1() = f() // `f` is called from C? + // fun g2() = { g1() } // ...or from C$g2$1 in the same package? + // inline fun g3() = g1() // ...or from some other package that calls g3? + // } + // However, for private ones we at least know where they're called, so just like inline lambdas, + // we can navigate there. + // + // TODO: this has some weird effects for inline functions in local classes, e.g. they + // access the capture fields (package-private) through accessors; this may or may not + // be necessary - local types should in theory not be usable outside the current file. + context is IrFunction && context.isInline -> { + val callSites = inlineFunctionCallSites[context] ?: return null + // Mark to avoid infinite recursion on self-recursive inline functions (those are only + // detected reliably by codegen; frontend only filters out simple cases). + inlineCallSites[context] = CallSite(null, approximateToPackage = false) + val commonCallSite = when { + callSites.isEmpty() -> CallSite(context.parent, false) + callSites.size == 1 -> CallSite(callSites.single(), false) + else -> { + @Suppress("NON_TAIL_RECURSIVE_CALL") + val results = callSites.map { findContainer(it, approximateToPackage = false) ?: return null } + // If all call sites are within a single class, use it. Otherwise, all scopes must be within + // the current file's package. + val single = results.first().takeIf { results.all { other -> it === other } } + CallSite(single ?: context.parent, single == null) + } + } + inlineCallSites[context] = commonCallSite + findContainer(commonCallSite.parent, approximateToPackage || commonCallSite.approximateToPackage) + } + // TODO: if this class is an object local to an inline function, it could be regenerated, + // so the scope depends on the declaration accessed (see KT-48508): + // class C { + // fun f1() + // inline fun inlineFun() = object { + // fun f2() {} + // fun g1() { + // f1() // this access can be anywhere + // f2() // can pretend this access is from C$foo$1 + // } + // } + // } + // Further complicating things, the accessor for `f1` cannot be in `C$inlineFun$1`, as otherwise + // the accessor itself will be regenerated (and thus not work) at `inlineFun` call sites. + context is IrClass && !approximateToPackage -> context + // Inline lambdas have already been moved out to the containing class, but we still need to check + // the containing function (again, see above), so navigate there instead. + context is IrDeclaration -> findContainer(context.parent, approximateToPackage) + // The only non-declaration parent should be the package. + else -> context as? IrPackageFragment + } + } +} + inline fun IrFile.findInlineLambdas( context: JvmBackendContext, crossinline onLambda: (IrFunctionReference, IrFunction, IrValueParameter, IrDeclaration) -> Unit ) = accept(object : IrInlineReferenceLocator(context) { override fun visitInlineLambda(argument: IrFunctionReference, callee: IrFunction, parameter: IrValueParameter, scope: IrDeclaration) = onLambda(argument, callee, parameter, scope) }, null) + +fun IrFile.findInlineCallSites(context: JvmBackendContext) = + IrInlineScopeResolver(context).apply { accept(this, null) } diff --git a/compiler/testData/codegen/bytecodeText/kt3845.kt b/compiler/testData/codegen/bytecodeText/kt3845.kt index be2bdbaf62d..38979ba1f75 100644 --- a/compiler/testData/codegen/bytecodeText/kt3845.kt +++ b/compiler/testData/codegen/bytecodeText/kt3845.kt @@ -33,9 +33,22 @@ class Example b3 = 1 b4 = 1 b5 = 1 + foo { a1 = 1 } + bar() } + + private inline fun bar() { a1 = 1 } } +inline fun foo(x: () -> Unit) = x() + // Every property should be accessed directly in this example because they all are final class properties with default accessors // 0 INVOKESPECIAL Example\.set + +// JVM_TEMPLATES +// ...except the access in `private inline fun`. +// 2 INVOKEVIRTUAL Example\.set + +// JVM_IR_TEMPLATES +// ...including the access in `private inline fun` which we know is only called from `init`. // 0 INVOKEVIRTUAL Example\.set