Reuse existing StringBuilder when appending concatenation

^KT-24949: Fixed
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
index f963b8d..6914ea5 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
@@ -49280,6 +49280,12 @@
         }
 
         @Test
+        @TestMetadata("kt24949.kt")
+        public void testKt24949() throws Exception {
+            runTest("compiler/testData/codegen/box/strings/kt24949.kt");
+        }
+
+        @Test
         @TestMetadata("kt2592.kt")
         public void testKt2592() throws Exception {
             runTest("compiler/testData/codegen/box/strings/kt2592.kt");
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
index 302f63f..697e2de 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
@@ -49280,6 +49280,12 @@
         }
 
         @Test
+        @TestMetadata("kt24949.kt")
+        public void testKt24949() throws Exception {
+            runTest("compiler/testData/codegen/box/strings/kt24949.kt");
+        }
+
+        @Test
         @TestMetadata("kt2592.kt")
         public void testKt2592() throws Exception {
             runTest("compiler/testData/codegen/box/strings/kt2592.kt");
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt
index ead6dab..9eccf90 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt
@@ -23,6 +23,7 @@
 import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
 import org.jetbrains.kotlin.ir.types.*
 import org.jetbrains.kotlin.ir.util.constructors
+import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
 import org.jetbrains.kotlin.ir.util.functions
 import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
 
@@ -173,7 +174,7 @@
         return appendAnyNFunction
     }
 
-    override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
+    private fun handleStringConcatenation(expression: IrStringConcatenation, stringBuilder: IrExpression): IrExpression {
         expression.transformChildrenVoid(this)
         return context.createJvmIrBuilder(currentScope!!, expression).run {
             // When `String.plus(Any?)` is invoked with receiver of platform type String or String with enhanced nullability, this could
@@ -191,7 +192,7 @@
 
                 arguments.size < MAX_STRING_CONCAT_DEPTH -> {
                     irCall(toStringFunction).apply {
-                        dispatchReceiver = appendWindow(arguments, irCall(constructor))
+                        dispatchReceiver = appendWindow(arguments, stringBuilder)
                     }
                 }
 
@@ -205,7 +206,7 @@
                     //      tmp.toString()
                     //  }
                     irBlock {
-                        val tmpStringBuilder = irTemporary(irCall(constructor))
+                        val tmpStringBuilder = irTemporary(stringBuilder)
                         val argsWindowed =
                             arguments.windowed(
                                 size = MAX_STRING_CONCAT_DEPTH,
@@ -222,6 +223,27 @@
         }
     }
 
+    override fun visitCall(expression: IrCall): IrExpression {
+        // Optimization: don't create a new StringBuilder when appending concatenation to existing StringBuilder. See KT-24949
+        if (isAppendOfStringConcatenation(expression)) {
+            return handleStringConcatenation(expression.getValueArgument(0) as IrStringConcatenation, expression.dispatchReceiver!!)
+        }
+        return super.visitCall(expression)
+    }
+
+    private fun isAppendOfStringConcatenation(expression: IrCall): Boolean {
+        return isAppendStringFunction(expression.symbol.owner) && expression.getValueArgument(0) is IrStringConcatenation
+    }
+
+    private fun isAppendStringFunction(function: IrSimpleFunction): Boolean {
+        return stringBuilder.fqNameWhenAvailable == function.dispatchReceiverParameter?.type?.classFqName
+                && function.isAppendFunction() && function.valueParameters[0].type.classOrNull == context.irBuiltIns.stringClass
+    }
+
+    override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
+        return handleStringConcatenation(expression, context.createJvmIrBuilder(currentScope!!, expression).irCall(constructor))
+    }
+
     private fun JvmIrBuilder.appendWindow(arguments: List<IrExpression>, stringBuilder0: IrExpression): IrExpression {
         return arguments.fold(stringBuilder0) { stringBuilder, arg ->
             val argument = normalizeArgument(arg)
diff --git a/compiler/testData/codegen/box/strings/kt24949.kt b/compiler/testData/codegen/box/strings/kt24949.kt
new file mode 100644
index 0000000..9f15484
--- /dev/null
+++ b/compiler/testData/codegen/box/strings/kt24949.kt
@@ -0,0 +1,24 @@
+// TARGET_BACKEND: JVM_IR
+
+fun testShortConcat(): String {
+    val c = "a"
+    val sb = StringBuilder()
+    sb.append(c + c + c)
+    return sb.toString()
+}
+
+fun testLongConcat(): String {
+    val c = "a"
+    val sb = StringBuilder()
+    sb.append(c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c + c)
+    return sb.toString()
+}
+
+fun box(): String {
+    if (testShortConcat() != "aaa") return "Fail 1"
+    if (testLongConcat() != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") return "Fail 2"
+    return "OK"
+}
+
+// CHECK_BYTECODE_TEXT
+// 2 NEW java/lang/StringBuilder
\ No newline at end of file
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 0b67d21..af567f4 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
@@ -49280,6 +49280,12 @@
         }
 
         @Test
+        @TestMetadata("kt24949.kt")
+        public void testKt24949() throws Exception {
+            runTest("compiler/testData/codegen/box/strings/kt24949.kt");
+        }
+
+        @Test
         @TestMetadata("kt2592.kt")
         public void testKt2592() throws Exception {
             runTest("compiler/testData/codegen/box/strings/kt2592.kt");