diff --git a/libraries/stdlib/wasm/builtins/kotlin/Char.kt b/libraries/stdlib/wasm/builtins/kotlin/Char.kt index d09a19ad8b7..f6f62e69932 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Char.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Char.kt @@ -103,7 +103,7 @@ public class Char private constructor(public val value: Char) : Comparable this.toInt().toDouble() override fun toString(): String = - String.unsafeFromCharArray(WasmCharArray(1).also { it.set(0, this) }) + String(WasmCharArray(1).also { it.set(0, this) }) override fun hashCode(): Int = this.toInt().hashCode() diff --git a/libraries/stdlib/wasm/builtins/kotlin/String.kt b/libraries/stdlib/wasm/builtins/kotlin/String.kt index 719c8bd98dd..eca5088036b 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/String.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/String.kt @@ -12,30 +12,31 @@ import kotlin.math.min * The `String` class represents character strings. All string literals in Kotlin programs, such as `"abc"`, are * implemented as instances of this class. */ -public class String private constructor(internal val chars: WasmCharArray) : Comparable, CharSequence { - public companion object { - // Note: doesn't copy the array, use with care. - internal fun unsafeFromCharArray(chars: WasmCharArray) = String(chars) - } +public class String private constructor( + private var leftIfInSum: String?, + private var _chars: WasmCharArray, +) : Comparable, CharSequence { + public companion object {} + + internal constructor(chars: WasmCharArray) : this(null, chars) { } /** * Returns a string obtained by concatenating this string with the string representation of the given [other] object. */ public operator fun plus(other: Any?): String { - val thisChars = chars - val otherChars = if (other is String) other.chars else other.toString().chars - val thisLen = thisChars.len() - val otherLen = otherChars.len() - if (otherLen == 0) return String(thisChars) - - val newChars = WasmCharArray(thisLen + otherLen) - copyWasmArray(thisChars, newChars, 0, 0, thisLen) - copyWasmArray(otherChars, newChars, 0, thisLen, otherLen) - return String(newChars) + val right = if (other is String) other else other.toString() + return String(this, right.chars) } - public override val length: Int - get() = chars.len() + public override val length: Int get() { + var currentLeftString = leftIfInSum + var currentLength = _chars.len() + while (currentLeftString != null) { + currentLength += currentLeftString._chars.len() + currentLeftString = currentLeftString.leftIfInSum + } + return currentLength + } /** * Returns the character of this string at the specified [index]. @@ -44,8 +45,36 @@ public class String private constructor(internal val chars: WasmCharArray) : Com * where the behavior is unspecified. */ public override fun get(index: Int): Char { - if (index < 0 || index >= chars.len()) throw IndexOutOfBoundsException() - return chars.get(index) + if (index < 0) throw IndexOutOfBoundsException() + val folded = chars + val length = folded.len() + if (index >= length) throw IndexOutOfBoundsException() + return folded.get(index) + } + + internal fun foldChars() { + val stringLength = this.length + val newArray = WasmCharArray(stringLength) + + var currentStartIndex = stringLength + var currentLeftString: String? = this + while (currentLeftString != null) { + val currentLeftStringChars = currentLeftString._chars + val currentLeftStringLen = currentLeftStringChars.len() + currentStartIndex -= currentLeftStringLen + copyWasmArray(currentLeftStringChars, newArray, 0, currentStartIndex, currentLeftStringLen) + currentLeftString = currentLeftString.leftIfInSum + } + check(currentStartIndex == 0) + _chars = newArray + leftIfInSum = null + } + + internal inline val chars: WasmCharArray get() { + if (leftIfInSum != null) { + foldChars() + } + return _chars } public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { @@ -84,17 +113,18 @@ public class String private constructor(internal val chars: WasmCharArray) : Com public override fun toString(): String = this public override fun hashCode(): Int { - val thisLength = length - - if (_hashCode != 0 || thisLength == null) - return _hashCode + if (_hashCode != 0) return _hashCode + val thisChars = chars + val thisLength = thisChars.len() + if (thisLength == 0) return 0 var hash = 0 var i = 0 while (i < thisLength) { - hash = 31 * hash + chars.get(i).toInt() + hash = 31 * hash + thisChars.get(i).toInt() i++ } + _hashCode = hash return _hashCode } @@ -103,5 +133,5 @@ public class String private constructor(internal val chars: WasmCharArray) : Com internal fun stringLiteral(startAddr: Int, length: Int): String { val array = WasmCharArray(length) unsafeRawMemoryToWasmCharArray(startAddr, 0, length, array) - return String.unsafeFromCharArray(array) + return String(array) } diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt index 7efda871db4..a9f36fde475 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt @@ -197,7 +197,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String { val stringLength = stringLength(x) val dstArray = WasmCharArray(stringLength) if (stringLength == 0) { - return String.unsafeFromCharArray(dstArray) + return String(dstArray) } val maxStringLength = unsafeGetScratchRawMemorySize() / CHAR_SIZE_BYTES @@ -212,7 +212,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String { jsExportStringToWasm(x, srcStartIndex, stringLength - srcStartIndex, memBuffer) unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, stringLength - srcStartIndex, dstArray) - return String.unsafeFromCharArray(dstArray) + return String(dstArray) } diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt index 5dc9f69ff61..cd6218758af 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt @@ -64,7 +64,7 @@ internal fun itoa32(inputValue: Int, radix: Int): String { if (sign == 1) buf.set(0, CharCodes.MINUS.code.toChar()) - return String.unsafeFromCharArray(buf) + return String(buf) } private fun utoaDecSimple(buffer: WasmCharArray, numInput: Int, offsetInput: Int) { @@ -144,7 +144,7 @@ internal fun itoa64(inputValue: Long, radix: Int): String { if (sign == 1) buf.set(0, CharCodes.MINUS.code.toChar()) - return String.unsafeFromCharArray(buf) + return String(buf) } // Count number of decimals for u64 values @@ -178,7 +178,7 @@ internal fun dtoa(value: Double): String { val size = dtoaCore(buf, value) val ret = WasmCharArray(size) buf.copyInto(ret, 0, 0, size) - return String.unsafeFromCharArray(ret) + return String(ret) } private fun dtoaCore(buffer: WasmCharArray, valueInp: Double): Int { diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt index 80bdcf2dd82..a0e3d553941 100644 --- a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt +++ b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt @@ -15,7 +15,7 @@ internal fun insertString(array: CharArray, destinationIndex: Int, value: String internal fun unsafeStringFromCharArray(array: CharArray, start: Int, size: Int): String { val copy = WasmCharArray(size) copyWasmArray(array.storage, copy, start, 0, size) - return String.unsafeFromCharArray(copy) + return String(copy) } internal fun insertInt(array: CharArray, start: Int, value: Int): Int { diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt index fd033c17941..618ad01f156 100644 --- a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt +++ b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt @@ -75,7 +75,7 @@ public actual fun String(chars: CharArray, offset: Int, length: Int): String { val copy = WasmCharArray(length) copyWasmArray(chars.storage, copy, offset, 0, length) - return String.unsafeFromCharArray(copy) + return String(copy) } /** @@ -88,7 +88,7 @@ public actual fun CharArray.concatToString(): String { val thisLength = thisStorage.len() val copy = WasmCharArray(thisLength) copyWasmArray(this.storage, copy, 0, 0, thisLength) - return String.unsafeFromCharArray(copy) + return String(copy) } /** @@ -109,7 +109,7 @@ public actual fun CharArray.concatToString(startIndex: Int = 0, endIndex: Int = val length = endIndex - startIndex val copy = WasmCharArray(length) copyWasmArray(this.storage, copy, startIndex, 0, length) - return String.unsafeFromCharArray(copy) + return String(copy) } /**