[K/N] Separate fun-ref to IrConstantObject conversion from FunctionReferenceLowering (KT-71046)

     * to help make FunctionReferenceLowering unified as much as possibly across the backends


Merge-request: KT-MR-17899
Merged-by: Alexey Glushko <aleksei.glushko@jetbrains.com>
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 d0fd30a..2393887 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
@@ -355,6 +355,13 @@
         prerequisite = setOf(localFunctionsPhase) // TODO: make weak dependency on `testProcessorPhase`
 )
 
+private val staticFunctionReferenceOptimizationPhase = createFileLoweringPhase(
+        lowering = ::StaticFunctionReferenceOptimization,
+        name = "StaticFunctionReferenceOptimization",
+        description = "Static function reference optimization",
+        prerequisite = setOf(functionReferencePhase, delegationPhase)
+)
+
 private val enumWhenPhase = createFileLoweringPhase(
         ::NativeEnumWhenLowering,
         name = "EnumWhen",
@@ -664,6 +671,7 @@
         testProcessorPhase.takeIf { context.config.configuration.getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) != TestRunnerKind.NONE },
         functionReferencePhase,
         delegationPhase,
+        staticFunctionReferenceOptimizationPhase,
         singleAbstractMethodPhase,
         enumWhenPhase,
         finallyBlocksPhase,
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt
index d46a302..f82d669 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt
@@ -455,16 +455,13 @@
             val constructor = buildConstructor()
             val arguments = functionReference.getArgumentsWithIr()
             val typeArguments = typeParametersFromEnclosingScope.map { it.defaultType }
-            val expression = if (arguments.isEmpty()) {
-                irBuilder.irConstantObject(clazz, emptyMap(), typeArguments)
-            } else {
-                irBuilder.irCallConstructor(constructor.symbol, typeArguments).apply {
-                    arguments.forEachIndexed { index, argument ->
-                        putValueArgument(index, argument.second)
-                    }
+            val constrCall = irBuilder.irCallConstructor(constructor.symbol, typeArguments).apply {
+                arguments.forEachIndexed { index, argument ->
+                    putValueArgument(index, argument.second)
                 }
             }
-            return BuiltFunctionReference(clazz, expression)
+
+            return BuiltFunctionReference(clazz, constrCall)
         }
 
         private fun IrBuilderWithScope.getDescription() : IrConstantValue {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StaticFunctionReferenceOptimization.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StaticFunctionReferenceOptimization.kt
new file mode 100644
index 0000000..957ad51
--- /dev/null
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StaticFunctionReferenceOptimization.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
+ * that can be found in the LICENSE file.
+ */
+
+package org.jetbrains.kotlin.backend.konan.lower
+
+import org.jetbrains.kotlin.backend.common.FileLoweringPass
+import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
+import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
+import org.jetbrains.kotlin.backend.konan.Context
+import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference
+import org.jetbrains.kotlin.ir.builders.irConstantObject
+import org.jetbrains.kotlin.ir.declarations.IrFile
+import org.jetbrains.kotlin.ir.expressions.*
+import org.jetbrains.kotlin.ir.util.constructedClass
+import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
+
+/**
+ * Function references are lowered into instantion of on object of some "callable" type.
+ * e.g. `call(::foo)` will be lowered to something like `call(foo$FUNCTION_REFERENCE$0())`.
+ *
+ * In some cases the object instantiated doesn't capture any context and in fact can be replaced with a constant object.
+ * That's what this optimization pass does.
+ */
+internal class StaticFunctionReferenceOptimization(val context: Context) : FileLoweringPass {
+    override fun lower(irFile: IrFile) {
+        irFile.transform(object : IrElementTransformerVoidWithContext() {
+            override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
+                expression.transformChildrenVoid(this)
+
+                val constructor = expression.symbol.owner
+                val constructedClass = constructor.constructedClass
+
+                if (isLoweredFunctionReference(constructedClass) && expression.valueArgumentsCount == 0) {
+                    val irBuilder = context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol,
+                            expression.startOffset, expression.endOffset)
+
+                    return irBuilder.irConstantObject(constructedClass, emptyMap(), expression.getClassTypeArguments().map { it!! })
+                } else {
+                    return expression
+                }
+            }
+        }, data = null)
+    }
+}
\ No newline at end of file