diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StringAppendGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/StringAppendGenerator.kt index 8b3d4a2269e..80de04dba73 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StringAppendGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StringAppendGenerator.kt @@ -7,14 +7,20 @@ package org.jetbrains.kotlin.codegen import com.google.common.collect.Sets import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE +import org.jetbrains.org.objectweb.asm.Handle import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import java.lang.StringBuilder class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAdapter) { + private val template = StringBuilder("") + private val paramTypes = arrayListOf() + @JvmOverloads fun genStringBuilderConstructorIfNeded(swap: Boolean = false) { if (useInvokeDynamic) return @@ -26,18 +32,22 @@ class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAd } } - fun addArgOnStack(type: Type) { - - } - - fun addCharConstant(value: Int) { - mv.iconst(value) - invokeAppend(Type.CHAR_TYPE) + fun addCharConstant(value: Char) { + if (!useInvokeDynamic) { + mv.iconst(value.toInt()) + invokeAppend(Type.CHAR_TYPE) + } else { + template.append(value) + } } fun addStringConstant(value: String) { - mv.aconst(value) - invokeAppend(JAVA_STRING_TYPE) + if (!useInvokeDynamic) { + mv.aconst(value) + invokeAppend(JAVA_STRING_TYPE) + } else { + template.append(value) + } } fun invokeAppend(type: Type) { @@ -48,12 +58,42 @@ class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAd "(" + stringBuilderAppendType(type) + ")Ljava/lang/StringBuilder;", false ) + } else { + paramTypes.add(type) + template.append("\u0001") + if (paramTypes.size == 200) { + // Concatenate current arguments into string + // because of `StringConcatFactory` limitation add use it as new argument for further processing: + // "The number of parameter slots in {@code concatType} is less than or equal to 200" + genToString() + } } } fun genToString() { if (!useInvokeDynamic) { mv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false) + } else { + //if state was flushed in `invokeAppend` do nothing + if (template.isEmpty() && paramTypes.size == 1 && paramTypes[0] == JAVA_STRING_TYPE) return + val bootstrap = Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/StringConcatFactory", + "makeConcatWithConstants", + "(Ljava/lang/invoke/MethodHandles\$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", + false + ) + + mv.invokedynamic( + "makeConcatWithConstants", + Type.getMethodDescriptor(JAVA_STRING_TYPE, *paramTypes.toTypedArray()), + bootstrap, + arrayOf(template.toString()) + ) + template.clear() + paramTypes.clear() + paramTypes.add(JAVA_STRING_TYPE) + template.append("\u0001") } } @@ -73,7 +113,8 @@ class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAd } } - fun create(state: GenerationState, mv: InstructionAdapter) = StringAppendGenerator(false, mv) + fun create(state: GenerationState, mv: InstructionAdapter) = + StringAppendGenerator(/*TODO: add flag*/state.target.bytecodeVersion >= JvmTarget.JVM_9.bytecodeVersion, mv) } } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/StringPlus.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/StringPlus.kt index 643ea25d48f..51162b879f3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/StringPlus.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/StringPlus.kt @@ -16,13 +16,37 @@ package org.jetbrains.kotlin.codegen.intrinsics -import org.jetbrains.kotlin.codegen.Callable -import org.jetbrains.kotlin.codegen.CallableMethod +import org.jetbrains.kotlin.codegen.* +import org.jetbrains.kotlin.psi.KtCallableReferenceExpression +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter class StringPlus : IntrinsicMethod() { override fun toCallable(method: CallableMethod): Callable = - createIntrinsicCallable(method) { - it.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "stringPlus", - "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false) + object : IntrinsicCallable(method) { + private lateinit var generator: StringAppendGenerator + + override fun invokeMethodWithArguments( + resolvedCall: ResolvedCall<*>, + receiver: StackValue, + codegen: ExpressionCodegen + ): StackValue { + generator = StringAppendGenerator.create(codegen.state, codegen.v) + return super.invokeMethodWithArguments(resolvedCall, receiver, codegen) } + + override fun genInvokeInstruction(v: InstructionAdapter) { + if (!generator.useInvokeDynamic) { + v.invokestatic( + IntrinsicMethods.INTRINSICS_CLASS_NAME, "stringPlus", + "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false + ) + } else { + generator.invokeAppend(AsmTypes.JAVA_STRING_TYPE) + generator.invokeAppend(AsmTypes.OBJECT_TYPE) + generator.genToString() + } + } + } } diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic.kt b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic.kt new file mode 100644 index 00000000000..5051d31e71b --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic.kt @@ -0,0 +1,21 @@ +// JVM_TARGET: 9 +class A + +inline fun test(s: (String) -> Unit) { + s("456") +} + +fun box(a: String, b: String?) { + val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() + + a.plus(b) + b?.plus(a) + val ref1 = a::plus + val ref2 = b::plus + + test("123"::plus) +} + +// 6 INVOKEDYNAMIC makeConcatWithConstants +// 0 append +// 0 stringPlus \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic200.kt b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic200.kt new file mode 100644 index 00000000000..49ff0fa31a1 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic200.kt @@ -0,0 +1,19 @@ +// JVM_TARGET: 9 + +fun box() { + val z = "0" + val result = z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z //200 +} + +// 1 INVOKEDYNAMIC makeConcatWithConstants +// 0 append +// 0 stringPlus \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic201.kt b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic201.kt new file mode 100644 index 00000000000..56caae49925 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic201.kt @@ -0,0 +1,18 @@ +// JVM_TARGET: 9 +fun box() { + val z = "0" + val result = z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + //200 + z //201 +} +// 2 INVOKEDYNAMIC makeConcatWithConstants +// 0 append +// 0 stringPlus \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamicConstants.kt b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamicConstants.kt new file mode 100644 index 00000000000..a93ca9e3c09 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/concatDynamicConstants.kt @@ -0,0 +1,14 @@ +// JVM_TARGET: 9 +class A + +inline fun test(s: (String) -> Unit) { + s("456") +} + +fun box(a: String, b: String?) { + val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() +} + +// 1INVOKEDYNAMIC makeConcatWithConstants +// 0 append +// 0 stringPlus \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/stringOperations/concatNotDynamic.kt b/compiler/testData/codegen/bytecodeText/stringOperations/concatNotDynamic.kt new file mode 100644 index 00000000000..e8a7ff8f369 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/stringOperations/concatNotDynamic.kt @@ -0,0 +1,9 @@ +// JVM_TARGET: 9 +fun box(a: String, b: String?) { + val sb = StringBuilder(); + sb.append("123") +} + +// 0 INVOKEDYNAMIC makeConcatWithConstants +// 1 append +// 0 stringPlus \ No newline at end of file diff --git a/compiler/testData/codegen/java9/box/concatDynamic200.kt b/compiler/testData/codegen/java9/box/concatDynamic200.kt new file mode 100644 index 00000000000..4f20cb626ff --- /dev/null +++ b/compiler/testData/codegen/java9/box/concatDynamic200.kt @@ -0,0 +1,21 @@ +// JVM_TARGET: 9 +fun box(): String { + val z = "0" + val result = z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z //200 + + return if (result.length != 200) + "fail: ${result.length}" else "OK" +} + +fun main() { + box().let { if (it != "OK") throw AssertionError(it) } +} diff --git a/compiler/testData/codegen/java9/box/concatDynamic201.kt b/compiler/testData/codegen/java9/box/concatDynamic201.kt new file mode 100644 index 00000000000..5efc7095305 --- /dev/null +++ b/compiler/testData/codegen/java9/box/concatDynamic201.kt @@ -0,0 +1,22 @@ +// JVM_TARGET: 9 +fun box(): String { + val z = "0" + val result = z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + z + //200 + z //201 + + return if (result.length != 201) + "fail: ${result.length}" else "OK" +} + +fun main() { + box().let { if (it != "OK") throw AssertionError(it) } +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 5634bcbce89..8b9a2814c57 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -4518,6 +4518,31 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/stringOperations/concat.kt"); } + @TestMetadata("concatDynamic.kt") + public void testConcatDynamic() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic.kt"); + } + + @TestMetadata("concatDynamic200.kt") + public void testConcatDynamic200() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic200.kt"); + } + + @TestMetadata("concatDynamic201.kt") + public void testConcatDynamic201() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic201.kt"); + } + + @TestMetadata("concatDynamicConstants.kt") + public void testConcatDynamicConstants() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamicConstants.kt"); + } + + @TestMetadata("concatNotDynamic.kt") + public void testConcatNotDynamic() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatNotDynamic.kt"); + } + @TestMetadata("constConcat.kt") public void testConstConcat() throws Exception { runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/Java9CodegenTest.kt b/compiler/tests/org/jetbrains/kotlin/codegen/Java9CodegenTest.kt index cb6ebaf0456..70e773fe5de 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/Java9CodegenTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/codegen/Java9CodegenTest.kt @@ -51,4 +51,14 @@ class Java9CodegenTest : AbstractBlackBoxCodegenTest() { loadFile() blackBox(true) } + + fun testConcatDynamic200() { + loadFile() + blackBox(true) + } + + fun testConcatDynamic201() { + loadFile() + blackBox(true) + } } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 66f7387048a..ad01b4a6a42 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -4486,6 +4486,31 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/stringOperations/concat.kt"); } + @TestMetadata("concatDynamic.kt") + public void testConcatDynamic() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic.kt"); + } + + @TestMetadata("concatDynamic200.kt") + public void testConcatDynamic200() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic200.kt"); + } + + @TestMetadata("concatDynamic201.kt") + public void testConcatDynamic201() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamic201.kt"); + } + + @TestMetadata("concatDynamicConstants.kt") + public void testConcatDynamicConstants() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatDynamicConstants.kt"); + } + + @TestMetadata("concatNotDynamic.kt") + public void testConcatNotDynamic() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/stringOperations/concatNotDynamic.kt"); + } + @TestMetadata("constConcat.kt") public void testConstConcat() throws Exception { runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt");