diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StringConcatGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/StringConcatGenerator.kt index 1c477f0c8cc..4e6f8e4404f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StringConcatGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StringConcatGenerator.kt @@ -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() @@ -183,28 +189,31 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte private fun fitRestrictions(items: List): ArrayList { val result = arrayListOf() + //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 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt index 67d563e0354..2a15e8b0022 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt @@ -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 { 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() + // 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() - 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 +} \ No newline at end of file