diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt index 1ae906bcbe3..cb9f9537d04 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmStringConcatenationLowering.kt @@ -15,13 +15,12 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.ir.JvmIrBuilder import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass -import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope -import org.jetbrains.kotlin.ir.builders.irCall -import org.jetbrains.kotlin.ir.builders.irImplicitCast -import org.jetbrains.kotlin.ir.builders.irString +import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrConstKind import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation import org.jetbrains.kotlin.ir.expressions.IrTypeOperator @@ -79,16 +78,23 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F private fun typeToAppendFunction(type: IrType): IrSimpleFunction = appendFunctions[type] ?: defaultAppendFunction - // There is no special append or valueOf function for byte and short on the JVM. - private fun IrBuilderWithScope.widenIntegerType(expression: IrExpression): IrExpression = + private fun IrBuilderWithScope.normalizeArgument(expression: IrExpression): IrExpression = if (expression.type.isByte() || expression.type.isShort()) { + // There is no special append or valueOf function for byte and short on the JVM. irImplicitCast(expression, context.irBuiltIns.intType) + } else if (expression is IrConst<*> && expression.kind == IrConstKind.String && (expression.value as String).length == 1) { + // PSI2IR generates const Strings for 1-length literals in string templates (e.g., the space between x and y in "$x $y"). + // We want to use the more efficient `append(Char)` function in such cases. This mirrors the behavior of the non-IR backend. + // + // In addition, this also means `append(Char)` will be used for the space in the following case: `x + " " + y`. The non-IR + // backend will still use `append(String)` in this case. + irChar((expression.value as String)[0]) } else { expression } private fun JvmIrBuilder.callToString(expression: IrExpression): IrExpression { - val argument = widenIntegerType(expression) + val argument = normalizeArgument(expression) val argumentType = if (argument.type.isPrimitiveType()) argument.type else context.irBuiltIns.anyNType return irCall(backendContext.ir.symbols.typeToStringValueOfFunction(argumentType)).apply { @@ -146,7 +152,7 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F else -> { var stringBuilder = irCall(constructor) for (arg in arguments) { - val argument = widenIntegerType(arg) + val argument = normalizeArgument(arg) val appendFunction = typeToAppendFunction(argument.type) stringBuilder = irCall(appendFunction).apply { dispatchReceiver = stringBuilder diff --git a/compiler/testData/codegen/bytecodeText/kt5016.kt b/compiler/testData/codegen/bytecodeText/kt5016.kt index 740e27de06b..0e8b8425f32 100644 --- a/compiler/testData/codegen/bytecodeText/kt5016.kt +++ b/compiler/testData/codegen/bytecodeText/kt5016.kt @@ -1,6 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// TODO KT-36638 Use 'java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;' when appending single character in JVM_IR - // KT-5016 wrong StringBuilder append method invoked class kt5016 { fun f1(name : String) : String { @@ -10,4 +7,6 @@ class kt5016 { // 0 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder // 2 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append // 1 INVOKEVIRTUAL java/lang/StringBuilder.toString diff --git a/compiler/testData/codegen/bytecodeText/kt5016int.kt b/compiler/testData/codegen/bytecodeText/kt5016int.kt index bab178f1dd6..08149223def 100644 --- a/compiler/testData/codegen/bytecodeText/kt5016int.kt +++ b/compiler/testData/codegen/bytecodeText/kt5016int.kt @@ -1,6 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// TODO KT-36638 Use 'java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;' when appending single character in JVM_IR - // KT-5016 wrong StringBuilder append method invoked class kt5016int { fun f1(num : Int) : String { @@ -11,4 +8,6 @@ class kt5016int { // 0 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder // 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder // 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append // 1 INVOKEVIRTUAL java/lang/StringBuilder.toString diff --git a/compiler/testData/codegen/bytecodeText/kt5016intOrNull.kt b/compiler/testData/codegen/bytecodeText/kt5016intOrNull.kt index 978f087ca91..ddf8f2c2f30 100644 --- a/compiler/testData/codegen/bytecodeText/kt5016intOrNull.kt +++ b/compiler/testData/codegen/bytecodeText/kt5016intOrNull.kt @@ -1,6 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// TODO KT-36638 Use 'java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;' when appending single character in JVM_IR - // KT-5016 wrong StringBuilder append method invoked class kt5016intOrNull { fun f1(num : Int?) : String { @@ -10,4 +7,6 @@ class kt5016intOrNull { // 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/Object;\)Ljava/lang/StringBuilder // 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append // 1 INVOKEVIRTUAL java/lang/StringBuilder.toString diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate.kt b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate.kt new file mode 100644 index 00000000000..55917f7370a --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate.kt @@ -0,0 +1,6 @@ +fun test(s: String, i: Int) = "${"x"}${s}${" "}${i}${"y"}" + +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 5 INVOKEVIRTUAL java/lang/StringBuilder.append diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate_2.kt b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate_2.kt new file mode 100644 index 00000000000..326a6c42655 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate_2.kt @@ -0,0 +1,6 @@ +fun test(s: String, i: Int) = "x${s} ${i}y" + +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 5 INVOKEVIRTUAL java/lang/StringBuilder.append diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringUsingPlus.kt b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringUsingPlus.kt new file mode 100644 index 00000000000..edf8831ee4d --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringUsingPlus.kt @@ -0,0 +1,16 @@ +fun test(s: String, i: Int) = "x" + s + " " + i + "y" + +// The IR is equivalent for this test and "useAppendCharForOneCharStringInTemplate*.kt" because there is an optimization for 1-length +// string literals in any string concatenation, whether using templates or + operator (see JvmStringConcatenationLowering). +// However, for the non-IR backend, `append(String)` will still be used for these 1-length strings. + +// JVM_TEMPLATES +// 4 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder +// 5 INVOKEVIRTUAL java/lang/StringBuilder.append + +// JVM_IR_TEMPLATES +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(Ljava/lang/String;\)Ljava/lang/StringBuilder +// 1 INVOKEVIRTUAL java/lang/StringBuilder.append \(I\)Ljava/lang/StringBuilder +// 3 INVOKEVIRTUAL java/lang/StringBuilder.append \(C\)Ljava/lang/StringBuilder +// 5 INVOKEVIRTUAL java/lang/StringBuilder.append diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 9ac1b20b9bf..f6eaa187b59 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -4249,6 +4249,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { public void testStringPlus() throws Exception { runTest("compiler/testData/codegen/bytecodeText/stringOperations/stringPlus.kt"); } + + @TestMetadata("useAppendCharForOneCharStringInTemplate.kt") + public void testUseAppendCharForOneCharStringInTemplate() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate.kt"); + } + + @TestMetadata("useAppendCharForOneCharStringInTemplate_2.kt") + public void testUseAppendCharForOneCharStringInTemplate_2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate_2.kt"); + } + + @TestMetadata("useAppendCharForOneCharStringUsingPlus.kt") + public void testUseAppendCharForOneCharStringUsingPlus() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringUsingPlus.kt"); + } } @TestMetadata("compiler/testData/codegen/bytecodeText/toArray") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 3d51e16fee5..56cecb0f278 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -4167,6 +4167,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { public void testStringPlus() throws Exception { runTest("compiler/testData/codegen/bytecodeText/stringOperations/stringPlus.kt"); } + + @TestMetadata("useAppendCharForOneCharStringInTemplate.kt") + public void testUseAppendCharForOneCharStringInTemplate() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate.kt"); + } + + @TestMetadata("useAppendCharForOneCharStringInTemplate_2.kt") + public void testUseAppendCharForOneCharStringInTemplate_2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringInTemplate_2.kt"); + } + + @TestMetadata("useAppendCharForOneCharStringUsingPlus.kt") + public void testUseAppendCharForOneCharStringUsingPlus() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/useAppendCharForOneCharStringUsingPlus.kt"); + } } @TestMetadata("compiler/testData/codegen/bytecodeText/toArray")