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 constant(value: String) = Item(JAVA_STRING_TYPE, ItemType.CONSTANT, value)
|
||||||
fun parameter(type: Type) = Item(type, ItemType.PARAMETER, "\u0001")
|
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>()
|
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> {
|
private fun fitRestrictions(items: List<Item>): ArrayList<Item> {
|
||||||
val result = arrayListOf<Item>()
|
val result = arrayListOf<Item>()
|
||||||
|
//Split long CONSTANT and INLINED_CONSTANT into smaller strings and convert them into CONSTANT
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
when (item.itemType) {
|
when (item.itemType) {
|
||||||
//split INLINED_CONSTANT becomes split CONSTANT one
|
//split INLINED_CONSTANT becomes split CONSTANT
|
||||||
ItemType.CONSTANT, ItemType.INLINED_CONSTANT -> splitStringConstant(item.value).forEach { part ->
|
ItemType.CONSTANT, ItemType.INLINED_CONSTANT ->
|
||||||
result.add(
|
if (item.fitEncodingLimit()) result.add(item) else splitStringConstant(item.value).forEach { part ->
|
||||||
Item(
|
result.add(
|
||||||
item.type,
|
Item(
|
||||||
ItemType.CONSTANT,
|
item.type,
|
||||||
part
|
ItemType.CONSTANT,
|
||||||
|
part
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
else -> result.add(item)
|
else -> result.add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipe = buildrecipe(result)
|
//Check restriction for recipe string
|
||||||
while (recipe.length >= MAX_CONST_STRING_PART_LIMIT) {
|
var recipe = buildRecipe(result)
|
||||||
val item = items.filter { it.itemType == ItemType.INLINED_CONSTANT }.maxByOrNull { it.value.length } ?: break
|
while (recipe.toString().encodedUTF8Size() > STRING_UTF8_ENCODING_BYTE_LIMIT) {
|
||||||
//move longest INLINED_CONSTANT to CONSTANT
|
val item = items.filter { it.itemType == ItemType.INLINED_CONSTANT }.maxByOrNull { it.encodedUTF8Size } ?: break
|
||||||
|
//move largest INLINED_CONSTANT to CONSTANT
|
||||||
item.itemType = ItemType.CONSTANT
|
item.itemType = ItemType.CONSTANT
|
||||||
recipe = buildrecipe(result)
|
recipe = buildRecipe(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -680,24 +680,52 @@ internal fun LabelNode.linkWithLabel(): LabelNode {
|
|||||||
fun linkedLabel(): Label = LabelNode().linkWithLabel().label
|
fun linkedLabel(): Label = LabelNode().linkWithLabel().label
|
||||||
|
|
||||||
// Strings in constant pool contain at most 2^16-1 = 65535 bytes.
|
// Strings in constant pool contain at most 2^16-1 = 65535 bytes.
|
||||||
// Non-BMP characters in UTF8 require at most 4 bytes.
|
const val STRING_UTF8_ENCODING_BYTE_LIMIT: Int = 65535
|
||||||
const val MAX_CONST_STRING_PART_LIMIT: Int = 65535 / 4
|
|
||||||
|
|
||||||
fun splitStringConstant(value: String): List<String> {
|
fun splitStringConstant(value: String): List<String> {
|
||||||
val length = value.length
|
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)
|
listOf(value)
|
||||||
} else {
|
} else {
|
||||||
|
val result = arrayListOf<String>()
|
||||||
|
|
||||||
// Split strings into parts, each of which satisfies JVM class file constant pool constraints.
|
// 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.
|
// Note that even if we split surrogate pairs between parts, they will be joined on concatenation.
|
||||||
var i = 0
|
var accumulatedSize = 0
|
||||||
val result = arrayListOf<String>()
|
var charOffsetInString = 0
|
||||||
while (i < length) {
|
var lastStringBeginning = 0
|
||||||
val j = minOf(i + MAX_CONST_STRING_PART_LIMIT, length)
|
while (charOffsetInString < length) {
|
||||||
result.add(value.substring(i, j))
|
val charCode = value[charOffsetInString].code
|
||||||
i += MAX_CONST_STRING_PART_LIMIT
|
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
|
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