IR Inliner: Inline `stub_for_inlining` at the right phase of double-inlining
For a function reference in a position of an inline function argument,
the `CommonInlineCallableReferenceToLambdaPhase` lowering generates
a lambda. The function expression in this lambda has an inline function
with the local visibility.
With the double inlining enabled, there is not enough information for
the inliner to deduce whether such a local inline function should
be inlined at the first or the second phase.
Using the visibility of the container inline function helps to solve
this problem.
^KT-69470
diff --git a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/CommonInlineCallableReferenceToLambdaPhase.kt b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/CommonInlineCallableReferenceToLambdaPhase.kt
index edb8c9f..4eaeb7b 100644
--- a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/CommonInlineCallableReferenceToLambdaPhase.kt
+++ b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/CommonInlineCallableReferenceToLambdaPhase.kt
@@ -26,7 +26,10 @@
for (parameter in declaration.valueParameters) {
if (parameter.isInlineParameter()) {
val defaultExpression = parameter.defaultValue?.expression ?: continue
- parameter.defaultValue?.expression = defaultExpression.transformToLambda(declaration)
+ parameter.defaultValue?.expression = defaultExpression.transformToLambda(
+ inlineFunctionVisibility = declaration.visibility,
+ scope = declaration
+ )
}
}
}
@@ -40,6 +43,9 @@
val owner = expression.symbol.owner
if (!owner.isInlineArrayConstructor(context.irBuiltIns)) return expression
- return expression.transformToLambda(data)
+ return expression.transformToLambda(
+ inlineFunctionVisibility = owner.visibility,
+ scope = data
+ )
}
}
\ No newline at end of file
diff --git a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
index 3aacae1f..945e044 100644
--- a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
+++ b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
@@ -769,5 +769,13 @@
/**
* Checks if the given function should be treated by 1st phase of inlining (inlining of private functions).
*/
-fun IrFunction.isConsideredAsPrivateForInlining(): Boolean =
- DescriptorVisibilities.isPrivate(visibility) || visibility == DescriptorVisibilities.LOCAL
+fun IrFunction.isConsideredAsPrivateForInlining(): Boolean {
+ if (visibility == DescriptorVisibilities.LOCAL) {
+ // In practice, there should not be inline functions with the local visibility after the fix of KT-69470.
+ // But we would like to preserve this check here. Because this way we can be sure that if such a function
+ // ever appears, it won't be left unnoticed.
+ return true
+ }
+
+ return DescriptorVisibilities.isPrivate(visibility)
+}
diff --git a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt
index 1c14f66..076113c 100644
--- a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt
+++ b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt
@@ -11,7 +11,7 @@
import org.jetbrains.kotlin.backend.common.lower.LoweredDeclarationOrigins
import org.jetbrains.kotlin.backend.common.lower.LoweredStatementOrigins
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
-import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
+import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
@@ -29,7 +29,7 @@
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.name.Name
-const val STUB_FOR_INLINING = "stub_for_inlining"
+val STUB_FOR_INLINING = Name.identifier("stub_for_inlining")
// This lowering transforms CR passed to inline function to lambda which would be inlined
//
@@ -56,7 +56,10 @@
if (inlineFunctionResolver.needsInlining(function)) {
for (parameter in function.valueParameters) {
if (parameter.isInlineParameter()) {
- expression.putValueArgument(parameter.index, expression.getValueArgument(parameter.index)?.transformToLambda(data))
+ val valueArgument = expression.getValueArgument(parameter.index)
+ val transformedValueArgument =
+ valueArgument?.transformToLambda(inlineFunctionVisibility = function.visibility, scope = data)
+ expression.putValueArgument(parameter.index, transformedValueArgument)
}
}
}
@@ -64,7 +67,7 @@
return expression
}
- protected fun IrExpression.transformToLambda(scope: IrDeclarationParent?) = when {
+ protected fun IrExpression.transformToLambda(inlineFunctionVisibility: DescriptorVisibility, scope: IrDeclarationParent?) = when {
this is IrBlock && origin.isInlinable -> apply {
// Already a lambda or similar, just mark it with an origin.
val reference = statements.last() as IrFunctionReference
@@ -74,7 +77,7 @@
this is IrFunctionReference -> {
// ::function -> { args... -> function(args...) }
- wrapFunction(symbol.owner).toLambda(this, scope!!)
+ wrapFunction(inlineFunctionVisibility, symbol.owner).toLambda(this, scope!!)
}
this is IrPropertyReference -> {
@@ -85,8 +88,8 @@
// References to generic synthetic Java properties aren't inlined in K1. Fixes KT-57103
typeArgumentsCount > 0 && isReferenceToSyntheticJavaProperty -> this
// ::property -> { receiver -> receiver.property }; prefer direct field access if allowed.
- field != null -> wrapField(field!!.owner).toLambda(this, scope!!)
- else -> wrapFunction(getter!!.owner).toLambda(this, scope!!)
+ field != null -> wrapField(inlineFunctionVisibility, field!!.owner).toLambda(this, scope!!)
+ else -> wrapFunction(inlineFunctionVisibility, getter!!.owner).toLambda(this, scope!!)
}
}
@@ -94,84 +97,88 @@
else -> this // not an inline argument
}
- private fun IrPropertyReference.wrapField(field: IrField): IrSimpleFunction =
- context.irFactory.buildFun {
- setSourceRange(this@wrapField)
- origin = LoweredDeclarationOrigins.INLINE_LAMBDA
- name = Name.identifier(STUB_FOR_INLINING)
- visibility = DescriptorVisibilities.LOCAL
- returnType = field.type
- isInline = true
- }.apply {
- body = context.createIrBuilder(symbol).run {
- val boundReceiver = dispatchReceiver ?: extensionReceiver
- val fieldReceiver = when {
- field.isStatic -> null
- boundReceiver != null -> irGet(addExtensionReceiver(boundReceiver.type))
- else -> irGet(addValueParameter("receiver", field.parentAsClass.defaultType))
- }
- irBlockBody {
- val exprToReturn = irGetField(fieldReceiver, field)
- +irReturn(exprToReturn)
- }
+ private fun IrPropertyReference.wrapField(
+ inlineFunctionVisibility: DescriptorVisibility,
+ field: IrField,
+ ): IrSimpleFunction = context.irFactory.buildFun {
+ setSourceRange(this@wrapField)
+ origin = LoweredDeclarationOrigins.INLINE_LAMBDA
+ name = STUB_FOR_INLINING
+ visibility = inlineFunctionVisibility // Use the visibility of the container inline function. KT-69470
+ returnType = field.type
+ isInline = true
+ }.apply {
+ body = context.createIrBuilder(symbol).run {
+ val boundReceiver = dispatchReceiver ?: extensionReceiver
+ val fieldReceiver = when {
+ field.isStatic -> null
+ boundReceiver != null -> irGet(addExtensionReceiver(boundReceiver.type))
+ else -> irGet(addValueParameter("receiver", field.parentAsClass.defaultType))
+ }
+ irBlockBody {
+ val exprToReturn = irGetField(fieldReceiver, field)
+ +irReturn(exprToReturn)
}
}
+ }
- private fun IrCallableReference<*>.wrapFunction(referencedFunction: IrFunction): IrSimpleFunction =
- context.irFactory.buildFun {
- setSourceRange(this@wrapFunction)
- origin = LoweredDeclarationOrigins.INLINE_LAMBDA
- name = Name.identifier(STUB_FOR_INLINING)
- visibility = DescriptorVisibilities.LOCAL
- returnType = ((type as IrSimpleType).arguments.last() as IrTypeProjection).type
- isSuspend = referencedFunction.isSuspend
- isInline = true
- }.apply {
- body = context.createIrBuilder(symbol, startOffset, endOffset).run {
- val boundReceiver = dispatchReceiver ?: extensionReceiver
- val boundReceiverParameter = when {
- dispatchReceiver != null -> referencedFunction.dispatchReceiverParameter
- extensionReceiver != null -> referencedFunction.extensionReceiverParameter
- else -> null
+ private fun IrCallableReference<*>.wrapFunction(
+ inlineFunctionVisibility: DescriptorVisibility,
+ referencedFunction: IrFunction,
+ ): IrSimpleFunction = context.irFactory.buildFun {
+ setSourceRange(this@wrapFunction)
+ origin = LoweredDeclarationOrigins.INLINE_LAMBDA
+ name = STUB_FOR_INLINING
+ visibility = inlineFunctionVisibility // Use the visibility of the container inline function. KT-69470
+ returnType = ((type as IrSimpleType).arguments.last() as IrTypeProjection).type
+ isSuspend = referencedFunction.isSuspend
+ isInline = true
+ }.apply {
+ body = context.createIrBuilder(symbol, startOffset, endOffset).run {
+ val boundReceiver = dispatchReceiver ?: extensionReceiver
+ val boundReceiverParameter = when {
+ dispatchReceiver != null -> referencedFunction.dispatchReceiverParameter
+ extensionReceiver != null -> referencedFunction.extensionReceiverParameter
+ else -> null
+ }
+
+ // TODO: could there be a star projection here?
+ val unboundArgumentTypes = (type as IrSimpleType).arguments.dropLast(1).map { (it as IrTypeProjection).type }
+ val argumentTypes = getAllArgumentsWithIr()
+ .filter { it.first != boundReceiverParameter }
+ .map { it.second }
+ .let { boundArguments ->
+ var i = 0
+ // if the argument is bound, then use the argument's type, otherwise take a type from reference's return type
+ boundArguments.map { it?.type ?: unboundArgumentTypes[i++] }
}
- // TODO: could there be a star projection here?
- val unboundArgumentTypes = (type as IrSimpleType).arguments.dropLast(1).map { (it as IrTypeProjection).type }
- val argumentTypes = getAllArgumentsWithIr()
- .filter { it.first != boundReceiverParameter }
- .map { it.second }
- .let { boundArguments ->
- var i = 0
- // if the argument is bound, then use the argument's type, otherwise take a type from reference's return type
- boundArguments.map { it?.type ?: unboundArgumentTypes[i++] }
- }
-
- irBlockBody {
- val exprToReturn = irCall(referencedFunction.symbol, returnType).apply {
- copyTypeArgumentsFrom(this@wrapFunction)
- for (parameter in referencedFunction.explicitParameters) {
- val next = valueParameters.size
- val getOnNewParameter = when {
- boundReceiverParameter == parameter -> irGet(addExtensionReceiver(boundReceiver!!.type))
- next >= argumentTypes.size ->
- error(
- "The number of parameters for reference and referenced function is different\n" +
- "Reference: ${this@wrapFunction.render()}\n" +
- "Referenced function: ${referencedFunction.render()}\n"
- )
- parameter.isVararg && parameter.type == argumentTypes[next] ->
- irGet(addValueParameter("p$next", argumentTypes[next]))
- parameter.isVararg && !parameter.hasDefaultValue() ->
- error("Callable reference with vararg should not appear at this stage.\n${this@wrapFunction.render()}")
- else -> irGet(addValueParameter("p$next", argumentTypes[next]))
- }
- putArgument(referencedFunction, parameter, getOnNewParameter)
+ irBlockBody {
+ val exprToReturn = irCall(referencedFunction.symbol, returnType).apply {
+ copyTypeArgumentsFrom(this@wrapFunction)
+ for (parameter in referencedFunction.explicitParameters) {
+ val next = valueParameters.size
+ val getOnNewParameter = when {
+ boundReceiverParameter == parameter -> irGet(addExtensionReceiver(boundReceiver!!.type))
+ next >= argumentTypes.size ->
+ error(
+ "The number of parameters for reference and referenced function is different\n" +
+ "Reference: ${this@wrapFunction.render()}\n" +
+ "Referenced function: ${referencedFunction.render()}\n"
+ )
+ parameter.isVararg && parameter.type == argumentTypes[next] ->
+ irGet(addValueParameter("p$next", argumentTypes[next]))
+ parameter.isVararg && !parameter.hasDefaultValue() ->
+ error("Callable reference with vararg should not appear at this stage.\n${this@wrapFunction.render()}")
+ else -> irGet(addValueParameter("p$next", argumentTypes[next]))
}
+ putArgument(referencedFunction, parameter, getOnNewParameter)
}
- +irReturn(exprToReturn)
}
+ +irReturn(exprToReturn)
}
}
+ }
private fun IrSimpleFunction.toLambda(original: IrCallableReference<*>, scope: IrDeclarationParent) =
context.createIrBuilder(symbol).irBlock(startOffset, endOffset, IrStatementOrigin.LAMBDA) {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
index 84457a8..326e25f 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
@@ -25,7 +25,6 @@
import org.jetbrains.kotlin.backend.konan.lower.InitializersLowering
import org.jetbrains.kotlin.backend.konan.optimizations.NativeForLoopsLowering
import org.jetbrains.kotlin.config.KlibConfigurationKeys
-import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile
@@ -77,15 +76,7 @@
checkInlineFunctionCallSites = { inlineFunctionUseSite ->
// Call sites of only non-private functions are allowed at this stage.
val inlineFunction = inlineFunctionUseSite.symbol.owner
- when {
- // TODO: remove this condition after the fix of KT-69470:
- inlineFunctionUseSite is IrFunctionReference &&
- inlineFunction.visibility == DescriptorVisibilities.LOCAL -> true // temporarily permitted
-
- inlineFunction.isConsideredAsPrivateForInlining() -> false // forbidden
-
- else -> true // permitted
- }
+ !inlineFunction.isConsideredAsPrivateForInlining()
}
).lower(module)
}