Calculate string encoding size more precisely

This commit is contained in:
Mikhael Bogdanov
2021-09-01 12:41:51 +02:00
committed by Space
parent 19474ee30f
commit f9b5414453
2 changed files with 60 additions and 23 deletions
@@ -33,6 +33,12 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
fun constant(value: String) = Item(JAVA_STRING_TYPE, ItemType.CONSTANT, value)
fun parameter(type: Type) = Item(type, ItemType.PARAMETER, "\u0001")
}
val encodedUTF8Size by lazy {
value.encodedUTF8Size()
}
fun fitEncodingLimit() = encodedUTF8Size <= STRING_UTF8_ENCODING_BYTE_LIMIT
}
private val items = arrayListOf<Item>()
@@ -183,28 +189,31 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
private fun fitRestrictions(items: List<Item>): ArrayList<Item> {
val result = arrayListOf<Item>()
//Split long CONSTANT and INLINED_CONSTANT into smaller strings and convert them into CONSTANT
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
//split INLINED_CONSTANT becomes split CONSTANT
ItemType.CONSTANT, ItemType.INLINED_CONSTANT ->
if (item.fitEncodingLimit()) result.add(item) else 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
//Check restriction for recipe string
var recipe = buildRecipe(result)
while (recipe.toString().encodedUTF8Size() > STRING_UTF8_ENCODING_BYTE_LIMIT) {
val item = items.filter { it.itemType == ItemType.INLINED_CONSTANT }.maxByOrNull { it.encodedUTF8Size } ?: break
//move largest INLINED_CONSTANT to CONSTANT
item.itemType = ItemType.CONSTANT
recipe = buildrecipe(result)
recipe = buildRecipe(result)
}
return result
@@ -680,24 +680,52 @@ 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
const val STRING_UTF8_ENCODING_BYTE_LIMIT: Int = 65535
fun splitStringConstant(value: String): List<String> {
val length = value.length
return if (length <= MAX_CONST_STRING_PART_LIMIT) {
//Each CHAR could be encoded maximum in 3 bytes
return if (length <= STRING_UTF8_ENCODING_BYTE_LIMIT / 3) {
listOf(value)
} else {
val result = arrayListOf<String>()
// 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
var accumulatedSize = 0
var charOffsetInString = 0
var lastStringBeginning = 0
while (charOffsetInString < length) {
val charCode = value[charOffsetInString].code
val encodedCharSize = when {
charCode in 1..127 -> 1
charCode <= 2047 -> 2
else -> 3
}
if (accumulatedSize + encodedCharSize > STRING_UTF8_ENCODING_BYTE_LIMIT) {
result.add(value.substring(lastStringBeginning, charOffsetInString))
lastStringBeginning = charOffsetInString
accumulatedSize = 0
}
accumulatedSize += encodedCharSize
++charOffsetInString
}
result.add(value.substring(lastStringBeginning, charOffsetInString))
result
}
}
fun String.encodedUTF8Size(): Int {
var result = 0
for (char in this) {
val charCode = char.code
when {
charCode in 1..127 -> result++
charCode <= 2047 -> result += 2
else -> result += 3
}
}
return result
}