Support large strings in indy-with-constants concatenation

#KT-47917 Fixed
This commit is contained in:
Mikhael Bogdanov
2021-09-01 12:57:15 +02:00
committed by Space
parent d2d3708827
commit 19474ee30f
19 changed files with 314 additions and 24 deletions
@@ -121,25 +121,16 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
false
)
val templateBuilder = StringBuilder()
items.forEach {
when (it.itemType) {
ItemType.PARAMETER ->
templateBuilder.append("\u0001")
ItemType.CONSTANT ->
templateBuilder.append("\u0002")
ItemType.INLINED_CONSTANT ->
templateBuilder.append(it.value)
}
}
val itemForGeneration = fitRestrictions(items)
val templateBuilder = buildRecipe(itemForGeneration)
val specialSymbolsInTemplate = items.filter { it.itemType == ItemType.CONSTANT }.map { it.value }
val specialSymbolsInTemplate = itemForGeneration.filter { it.itemType == ItemType.CONSTANT }.map { it.value }
mv.invokedynamic(
"makeConcatWithConstants",
Type.getMethodDescriptor(
JAVA_STRING_TYPE,
*items.filter { it.itemType == ItemType.PARAMETER }.map { it.type }.toTypedArray()
*itemForGeneration.filter { it.itemType == ItemType.PARAMETER }.map { it.type }.toTypedArray()
),
bootstrap,
arrayOf(templateBuilder.toString()) + specialSymbolsInTemplate
@@ -172,6 +163,51 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
items.add(Item.parameter(JAVA_STRING_TYPE))
paramSlots = JAVA_STRING_TYPE.size
}
}
private fun buildRecipe(itemForGeneration: ArrayList<Item>): StringBuilder {
val templateBuilder = StringBuilder()
itemForGeneration.forEach {
when (it.itemType) {
ItemType.PARAMETER ->
templateBuilder.append("\u0001")
ItemType.CONSTANT ->
templateBuilder.append("\u0002")
ItemType.INLINED_CONSTANT ->
templateBuilder.append(it.value)
}
}
return templateBuilder
}
private fun fitRestrictions(items: List<Item>): ArrayList<Item> {
val result = arrayListOf<Item>()
items.forEach { item ->
when (item.itemType) {
//split INLINED_CONSTANT becomes split CONSTANT one
ItemType.CONSTANT, ItemType.INLINED_CONSTANT -> splitStringConstant(item.value).forEach { part ->
result.add(
Item(
item.type,
ItemType.CONSTANT,
part
)
)
}
else -> result.add(item)
}
}
var recipe = buildrecipe(result)
while (recipe.length >= MAX_CONST_STRING_PART_LIMIT) {
val item = items.filter { it.itemType == ItemType.INLINED_CONSTANT }.maxByOrNull { it.value.length } ?: break
//move longest INLINED_CONSTANT to CONSTANT
item.itemType = ItemType.CONSTANT
recipe = buildrecipe(result)
}
return result
}
companion object {
@@ -678,3 +678,26 @@ internal fun LabelNode.linkWithLabel(): LabelNode {
}
fun linkedLabel(): Label = LabelNode().linkWithLabel().label
// Strings in constant pool contain at most 2^16-1 = 65535 bytes.
// Non-BMP characters in UTF8 require at most 4 bytes.
const val MAX_CONST_STRING_PART_LIMIT: Int = 65535 / 4
fun splitStringConstant(value: String): List<String> {
val length = value.length
return if (length <= MAX_CONST_STRING_PART_LIMIT) {
listOf(value)
} else {
// Split strings into parts, each of which satisfies JVM class file constant pool constraints.
// Note that even if we split surrogate pairs between parts, they will be joined on concatenation.
var i = 0
val result = arrayListOf<String>()
while (i < length) {
val j = minOf(i + MAX_CONST_STRING_PART_LIMIT, length)
result.add(value.substring(i, j))
i += MAX_CONST_STRING_PART_LIMIT
}
result
}
}