Basic invokedynamic string concatenation support
This commit is contained in:
@@ -7,14 +7,20 @@ package org.jetbrains.kotlin.codegen
|
|||||||
|
|
||||||
import com.google.common.collect.Sets
|
import com.google.common.collect.Sets
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
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
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE
|
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.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
|
||||||
class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAdapter) {
|
class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAdapter) {
|
||||||
|
|
||||||
|
private val template = StringBuilder("")
|
||||||
|
private val paramTypes = arrayListOf<Type>()
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun genStringBuilderConstructorIfNeded(swap: Boolean = false) {
|
fun genStringBuilderConstructorIfNeded(swap: Boolean = false) {
|
||||||
if (useInvokeDynamic) return
|
if (useInvokeDynamic) return
|
||||||
@@ -26,18 +32,22 @@ class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addArgOnStack(type: Type) {
|
fun addCharConstant(value: Char) {
|
||||||
|
if (!useInvokeDynamic) {
|
||||||
}
|
mv.iconst(value.toInt())
|
||||||
|
invokeAppend(Type.CHAR_TYPE)
|
||||||
fun addCharConstant(value: Int) {
|
} else {
|
||||||
mv.iconst(value)
|
template.append(value)
|
||||||
invokeAppend(Type.CHAR_TYPE)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addStringConstant(value: String) {
|
fun addStringConstant(value: String) {
|
||||||
mv.aconst(value)
|
if (!useInvokeDynamic) {
|
||||||
invokeAppend(JAVA_STRING_TYPE)
|
mv.aconst(value)
|
||||||
|
invokeAppend(JAVA_STRING_TYPE)
|
||||||
|
} else {
|
||||||
|
template.append(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeAppend(type: Type) {
|
fun invokeAppend(type: Type) {
|
||||||
@@ -48,12 +58,42 @@ class StringAppendGenerator(val useInvokeDynamic: Boolean, val mv: InstructionAd
|
|||||||
"(" + stringBuilderAppendType(type) + ")Ljava/lang/StringBuilder;",
|
"(" + stringBuilderAppendType(type) + ")Ljava/lang/StringBuilder;",
|
||||||
false
|
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() {
|
fun genToString() {
|
||||||
if (!useInvokeDynamic) {
|
if (!useInvokeDynamic) {
|
||||||
mv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,13 +16,37 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.intrinsics
|
package org.jetbrains.kotlin.codegen.intrinsics
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.Callable
|
import org.jetbrains.kotlin.codegen.*
|
||||||
import org.jetbrains.kotlin.codegen.CallableMethod
|
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() {
|
class StringPlus : IntrinsicMethod() {
|
||||||
override fun toCallable(method: CallableMethod): Callable =
|
override fun toCallable(method: CallableMethod): Callable =
|
||||||
createIntrinsicCallable(method) {
|
object : IntrinsicCallable(method) {
|
||||||
it.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "stringPlus",
|
private lateinit var generator: StringAppendGenerator
|
||||||
"(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false)
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
+14
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -4518,6 +4518,31 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/stringOperations/concat.kt");
|
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")
|
@TestMetadata("constConcat.kt")
|
||||||
public void testConstConcat() throws Exception {
|
public void testConstConcat() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt");
|
runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt");
|
||||||
|
|||||||
@@ -51,4 +51,14 @@ class Java9CodegenTest : AbstractBlackBoxCodegenTest() {
|
|||||||
loadFile()
|
loadFile()
|
||||||
blackBox(true)
|
blackBox(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testConcatDynamic200() {
|
||||||
|
loadFile()
|
||||||
|
blackBox(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcatDynamic201() {
|
||||||
|
loadFile()
|
||||||
|
blackBox(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -4486,6 +4486,31 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/stringOperations/concat.kt");
|
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")
|
@TestMetadata("constConcat.kt")
|
||||||
public void testConstConcat() throws Exception {
|
public void testConstConcat() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt");
|
runTest("compiler/testData/codegen/bytecodeText/stringOperations/constConcat.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user