Devirtualization fails to eliminate boxing in function reference context

^KT-49847 Fixed

Merge-request: KT-MR-6460
Merged-by: Vladimir Sukharev <Vladimir.Sukharev@jetbrains.com>
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
index f77b2e2..feb10fc 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
@@ -41913,6 +41913,12 @@
             }
 
             @Test
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
+
+            @Test
             @TestMetadata("genericMethodSignature.kt")
             public void testGenericMethodSignature() throws Exception {
                 runTest("compiler/testData/codegen/box/reflection/genericSignature/genericMethodSignature.kt");
diff --git a/compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt b/compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt
new file mode 100644
index 0000000..7ed907e
--- /dev/null
+++ b/compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt
@@ -0,0 +1,12 @@
+// WITH_REFLECT
+
+package test
+
+fun <T> foo(x: T) = x
+
+fun box(): String {
+    val bar: kotlin.reflect.KFunction1<Int, Int> = ::foo
+    val returnType = bar.returnType
+    if (returnType.toString() != "T") return returnType.toString()
+    return "OK"
+}
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java
index 53d078e..f18c7ca 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java
@@ -41307,6 +41307,12 @@
             }
 
             @Test
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
+
+            @Test
             @TestMetadata("genericMethodSignature.kt")
             public void testGenericMethodSignature() throws Exception {
                 runTest("compiler/testData/codegen/box/reflection/genericSignature/genericMethodSignature.kt");
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
index b5c9a1d..3a71bb9 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
@@ -41913,6 +41913,12 @@
             }
 
             @Test
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
+
+            @Test
             @TestMetadata("genericMethodSignature.kt")
             public void testGenericMethodSignature() throws Exception {
                 runTest("compiler/testData/codegen/box/reflection/genericSignature/genericMethodSignature.kt");
diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
index 54c7c97..bb18a2a 100644
--- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
+++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
@@ -33159,6 +33159,11 @@
                 runTest("compiler/testData/codegen/box/reflection/genericSignature/genericBackingFieldSignature.kt");
             }
 
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
+
             @TestMetadata("genericMethodSignature.kt")
             public void testGenericMethodSignature() throws Exception {
                 runTest("compiler/testData/codegen/box/reflection/genericSignature/genericMethodSignature.kt");
diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java
index 3b97544..c8b99ca 100644
--- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java
+++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java
@@ -31023,6 +31023,12 @@
             public void testAllFilesPresentInGenericSignature() throws Exception {
                 KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/genericSignature"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
             }
+
+            @Test
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
         }
 
         @Nested
diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java
index 47a8581..5cdea63 100644
--- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java
+++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java
@@ -31125,6 +31125,12 @@
             public void testAllFilesPresentInGenericSignature() throws Exception {
                 KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/genericSignature"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
             }
+
+            @Test
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
         }
 
         @Nested
diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java
index 941399f..098976d 100644
--- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java
+++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java
@@ -27852,6 +27852,11 @@
             public void testAllFilesPresentInGenericSignature() throws Exception {
                 KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/genericSignature"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
             }
+
+            @TestMetadata("genericFunctionReferenceSignature.kt")
+            public void testGenericFunctionReferenceSignature() throws Exception {
+                runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+            }
         }
 
         @TestMetadata("compiler/testData/codegen/box/reflection/isInstance")
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 4c1b3188..2d8d4db 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
@@ -7,7 +7,6 @@
 
 import org.jetbrains.kotlin.backend.common.FileLoweringPass
 import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
-import org.jetbrains.kotlin.backend.common.ir.*
 import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
 import org.jetbrains.kotlin.backend.common.pop
 import org.jetbrains.kotlin.backend.common.push
@@ -184,6 +183,14 @@
         private val typeArgumentsMap = referencedFunction.typeParameters.associate { typeParam ->
             typeParam.symbol to functionReference.getTypeArgument(typeParam.index)!!
         }
+        private val functionParameterAndReturnTypes = (functionReference.type as IrSimpleType).arguments.map {
+            when (it) {
+                is IrTypeProjection -> it.type
+                else -> context.irBuiltIns.anyNType
+            }
+        }
+        private val functionParameterTypes = functionParameterAndReturnTypes.dropLast(1)
+        private val functionReturnType = functionParameterAndReturnTypes.last()
 
         private val isLambda = functionReference.origin.isLambda
         private val isKFunction = functionReference.type.isKFunction()
@@ -241,9 +248,9 @@
 
         private fun buildClass(): IrClass {
             val superClass = when {
-                isKSuspendFunction -> kSuspendFunctionImplSymbol.typeWith(referencedFunction.returnType)
+                isKSuspendFunction -> kSuspendFunctionImplSymbol.typeWith(functionReturnType)
                 isLambda -> irBuiltIns.anyType
-                else -> kFunctionImplSymbol.typeWith(referencedFunction.returnType)
+                else -> kFunctionImplSymbol.typeWith(functionReturnType)
             }
             val superTypes = mutableListOf(superClass)
             if (samSuperType != null) {
@@ -253,16 +260,15 @@
                 buildInvokeMethod(sam.owner)
             } else {
                 val numberOfParameters = unboundFunctionParameters.size
-                val functionParameterTypes = unboundFunctionParameters.map { it.type }
                 val functionClass: IrClass?
                 val suspendFunctionClass: IrClass?
                 if (isKSuspendFunction) {
                     functionClass = null
                     suspendFunctionClass = symbols.kSuspendFunctionN(numberOfParameters).owner
-                    superTypes += suspendFunctionClass.typeWith(functionParameterTypes + referencedFunction.returnType)
+                    superTypes += suspendFunctionClass.typeWith(functionParameterAndReturnTypes)
                 } else {
                     functionClass = (if (isKFunction) symbols.kFunctionN(numberOfParameters) else symbols.functionN(numberOfParameters)).owner
-                    superTypes += functionClass.typeWith(functionParameterTypes + referencedFunction.returnType)
+                    superTypes += functionClass.typeWith(functionParameterAndReturnTypes)
                     val lastParameterType = unboundFunctionParameters.lastOrNull()?.type
                     if (lastParameterType?.classifierOrNull != continuationClassSymbol)
                         suspendFunctionClass = null
@@ -442,7 +448,7 @@
                     superFunction.name,
                     DescriptorVisibilities.PRIVATE,
                     Modality.FINAL,
-                    referencedFunction.returnType,
+                    functionReturnType,
                     isInline = false,
                     isExternal = false,
                     isTailrec = false,
@@ -462,7 +468,7 @@
 
                 valueParameters += superFunction.valueParameters.mapIndexed { index, parameter ->
                     parameter.copyTo(function, DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL, index,
-                            type = parameter.type.substitute(typeArgumentsMap))
+                            type = functionParameterTypes[index])
                 }
 
                 overriddenSymbols += superFunction.symbol
@@ -497,6 +503,10 @@
                                     }
                                 }
                                 assert(unboundIndex == valueParameters.size) { "Not all arguments of <invoke> are used" }
+
+                                referencedFunction.typeParameters.forEach { typeParam ->
+                                    putTypeArgument(typeParam.index, functionReference.getTypeArgument(typeParam.index)!!)
+                                }
                             }
                     )
                 }
diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle
index 87de7f6..9f1c55a 100644
--- a/kotlin-native/backend.native/tests/build.gradle
+++ b/kotlin-native/backend.native/tests/build.gradle
@@ -6152,6 +6152,46 @@
     annotatedSource = project.file('filecheck/replace_invoke_with_call.kt')
 }
 
+fileCheckTest("filecheck_kt49847_simple_function_reference") {
+    annotatedSource = project.file('filecheck/kt49847_simple_function_reference.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_sam_Any") {
+    annotatedSource = project.file('filecheck/kt49847_sam_Any.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_sam_Int") {
+    annotatedSource = project.file('filecheck/kt49847_sam_Int.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_sam_Any_generic") {
+    annotatedSource = project.file('filecheck/kt49847_sam_Any_generic.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_sam_Int_generic") {
+    annotatedSource = project.file('filecheck/kt49847_sam_Int_generic.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_class") {
+    annotatedSource = project.file('filecheck/kt49847_class.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_generic") {
+    annotatedSource = project.file('filecheck/kt49847_generic.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
+fileCheckTest("filecheck_kt49847_generic_receiver") {
+    annotatedSource = project.file('filecheck/kt49847_generic_receiver.kt')
+    enabled = project.globalTestArgs.contains('-opt')
+}
+
 fileCheckTest("filecheck_intrinsics") {
     annotatedSource = project.file('filecheck/intrinsics.kt')
 }
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt
new file mode 100644
index 0000000..66bb8f6
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+class C {
+    fun foo(x: Int) = x
+}
+
+// CHECK: define void @"kfun:#main(){}"()
+// CHECK-NOT: Int-box
+//   TODO Remove next check after fix of https://youtrack.jetbrains.com/issue/KT-53100/Optimization-needed-T-unboxCONSTANTPRIMITIVEx-T-x
+// CHECK: Int-unbox
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+fun main() {
+    val c = C()
+    val fooref = c::foo
+    if( fooref(42) == 42)
+        println("ok")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt
new file mode 100644
index 0000000..6b929d1
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+class C<T> {
+    fun foo(x: T) = x
+}
+
+// CHECK: define void @"kfun:#main(){}"()
+// CHECK-NOT: Int-box
+//   TODO Remove next check after fix of https://youtrack.jetbrains.com/issue/KT-53100/Optimization-needed-T-unboxCONSTANTPRIMITIVEx-T-x
+// CHECK: Int-unbox
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+fun main() {
+    val c = C<Int>()
+    val fooref = c::foo
+    if( fooref(42) == 42)
+        println("ok")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt
new file mode 100644
index 0000000..cfc2828
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+// CHECK: define internal void @"kfun:$foo$FUNCTION_REFERENCE$0.<init>#internal"
+// CHECK-SAME: i32
+
+fun <T> T.foo() { println(this) }
+
+fun main() {
+    println(5::foo)
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt
new file mode 100644
index 0000000..7ce336a
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+fun interface Foo {
+    fun bar(x: Int): Any
+}
+
+fun baz(x: Any): Int = x.hashCode()
+
+// CHECK: define void @"kfun:#main(){}"()
+// Boxing/unboxing need to be used now due to non-devirtualized call
+// CHECK: Int-box
+
+//  TODO  Remove two next checks, when advanced optimization of Int-unbox(Int-box(x)) would be done for snippet like:
+//  TODO  VAR IR_TEMPORARY_VARIABLE name:arg0 type:kotlin.Any [val]
+//  TODO    BLOCK type=kotlin.Any origin=null
+//  TODO      CALL <Int-box>
+//  TODO        GET_VAR 'val arg1: kotlin.Int [val]'
+//  TODO  CALL <Int-unbox>
+//  TODO    GET_VAR 'val arg0: kotlin.Any [val]'
+// CHECK: Int-box
+// CHECK: Int-unbox
+
+// CHECK-NOT: Int-box
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+
+fun main() {
+    val foo: Foo = Foo(::baz)
+    if( foo.bar(42) == 42 )
+        println("passed")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt
new file mode 100644
index 0000000..4303c3c
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+fun interface Foo<T> {
+    fun bar(x: T): Any
+}
+
+fun baz(x: Any): Int = x.hashCode()
+
+// CHECK: define void @"kfun:#main(){}"()
+// Boxing/unboxing need to be used now due to non-devirtualized call
+// CHECK: Int-box
+// CHECK-NOT: Int-box
+// CHECK: Int-unbox
+// CHECK-NOT: Int-unbox
+// CHECK-NOT: Int-box
+// CHECK: ret void
+fun main() {
+    val foo: Foo<Int> = Foo(::baz)
+    if( foo.bar(42) == 42 )
+        println("passed")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt
new file mode 100644
index 0000000..b0abed2
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+fun interface Foo {
+    fun bar(x: Int): Int
+}
+
+fun baz(x: Int): Int = x.hashCode()
+
+// CHECK: define void @"kfun:#main(){}"()
+// CHECK-NOT: Int-box
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+fun main() {
+    val foo: Foo = Foo(::baz)
+    if( foo.bar(42) == 42 )
+        println("passed")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt
new file mode 100644
index 0000000..c857547
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+fun interface Foo<T> {
+    fun bar(x: T): Int
+}
+
+fun baz(x: Any): Int = x.hashCode()
+
+// CHECK: define void @"kfun:#main(){}"()
+// CHECK-NOT: Int-box
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+fun main() {
+    val foo: Foo<Int> = Foo(::baz)
+    if( foo.bar(42) == 42 )
+        println("passed")
+}
diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt
new file mode 100644
index 0000000..703546b
--- /dev/null
+++ b/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+fun plus1(x: Int) = x + 1
+
+// CHECK: define void @"kfun:#main(){}"()
+// CHECK-NOT: Int-box
+// CHECK-NOT: Int-unbox
+// CHECK: ret void
+fun main() {
+    val ref = ::plus1
+    var y = 0
+    repeat(100000) {
+        y += ref(it)  // Should be devirtualized and invoked without boxing/unboxing (`Int-box`/`Int-unbox`)
+    }
+    if (y > 999999)
+        println("y > 999999")
+}
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java
index cfd1761..8c2af2c 100644
--- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java
@@ -34116,6 +34116,12 @@
                 public void testAllFilesPresentInGenericSignature() throws Exception {
                     KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/genericSignature"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.NATIVE, true);
                 }
+
+                @Test
+                @TestMetadata("genericFunctionReferenceSignature.kt")
+                public void testGenericFunctionReferenceSignature() throws Exception {
+                    runTest("compiler/testData/codegen/box/reflection/genericSignature/genericFunctionReferenceSignature.kt");
+                }
             }
 
             @Nested