Calculate string encoding size more precisely
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user