[WASM] Replace String storage type from CharArray to WasmCharArray

This commit is contained in:
Igor Yakovlev
2022-03-02 20:50:19 +01:00
committed by teamcity
parent fb0819d490
commit d881f0d2a6
8 changed files with 113 additions and 55 deletions
@@ -93,7 +93,7 @@ public class Char private constructor(public val value: Char) : Comparable<Char>
this.toInt().toDouble()
override fun toString(): String =
String.unsafeFromCharArray(charArrayOf(this))
String.unsafeFromCharArray(WasmCharArray(1).also { it.set(0, this) })
override fun hashCode(): Int =
this.toInt().hashCode()
+31 -10
View File
@@ -12,20 +12,27 @@ 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: CharArray) : Comparable<String>, CharSequence {
public class String private constructor(internal val chars: WasmCharArray) : Comparable<String>, CharSequence {
public companion object {
// Note: doesn't copy the array, use with care.
internal fun unsafeFromCharArray(chars: CharArray) = String(chars)
internal fun unsafeFromCharArray(chars: WasmCharArray) = String(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 =
String(chars + other.toString().chars)
public operator fun plus(other: Any?): String {
val otherChars = if (other is String) other.chars else other.toString().chars
val newCharsLen = chars.len() + otherChars.len()
val newChars = WasmCharArray(newCharsLen)
newChars.fill(newCharsLen) { i ->
if (i < chars.len()) chars.get(i) else otherChars.get(i - chars.len())
}
return String(newChars)
}
public override val length: Int
get() = chars.size
get() = chars.len()
/**
* Returns the character of this string at the specified [index].
@@ -33,10 +40,20 @@ public class String private constructor(internal val chars: CharArray) : Compara
* If the [index] is out of bounds of this string, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
public override fun get(index: Int): Char = chars[index]
public override fun get(index: Int): Char {
if (index < 0 || index >= chars.len()) throw IndexOutOfBoundsException()
return chars.get(index)
}
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return String(chars.sliceArray(startIndex until endIndex))
val actualStartIndex = startIndex.coerceAtLeast(0)
val actualEndIndex = endIndex.coerceAtMost(chars.len())
val newCharsLen = actualEndIndex - actualStartIndex
val newChars = WasmCharArray(newCharsLen)
newChars.fill(newCharsLen) { i ->
chars.get(actualStartIndex + i)
}
return String(newChars)
}
public override fun compareTo(other: String): Int {
@@ -62,12 +79,16 @@ public class String private constructor(internal val chars: CharArray) : Compara
public override fun hashCode(): Int {
if (_hashCode != 0 || this.isEmpty())
return _hashCode
var hash = 0
for (c in chars)
hash = 31 * hash + c.toInt()
var i = 0
while (i < chars.len()) {
hash = 31 * hash + chars.get(i).toInt()
i++
}
_hashCode = hash
return _hashCode
}
}
internal fun stringLiteral(startAddr: Int, length: Int) = String.unsafeFromCharArray(unsafeRawMemoryToCharArray(startAddr, length))
internal fun stringLiteral(startAddr: Int, length: Int) = String.unsafeFromCharArray(unsafeRawMemoryToWasmCharArray(startAddr, length))
@@ -156,7 +156,7 @@ internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String {
val length = stringLength(x)
val addr = unsafeGetScratchRawMemory(INT_SIZE_BYTES + length * CHAR_SIZE_BYTES)
jsWriteStringIntoMemory(x, addr)
return String.unsafeFromCharArray(unsafeRawMemoryToCharArray(addr, length))
return String.unsafeFromCharArray(unsafeRawMemoryToWasmCharArray(addr, length))
}
@@ -14,7 +14,7 @@ internal fun exportString(src: String?): Int {
val retAddr = unsafeGetScratchRawMemory(INT_SIZE_BYTES + src.length * CHAR_SIZE_BYTES)
wasm_i32_store(retAddr, src.length)
unsafeCharArrayToRawMemory(src.chars, retAddr + INT_SIZE_BYTES)
unsafeWasmCharArrayToRawMemory(src.chars, retAddr + INT_SIZE_BYTES)
return retAddr
}
@@ -59,18 +59,18 @@ internal fun itoa32(inputValue: Int, radix: Int): String {
val absValue = if (sign == 1) -inputValue else inputValue
val decimals = decimalCount32(absValue) + sign
val buf = CharArray(decimals)
val buf = WasmCharArray(decimals)
utoaDecSimple(buf, absValue, decimals)
if (sign == 1)
buf[0] = CharCodes.MINUS.code.toChar()
buf.set(0, CharCodes.MINUS.code.toChar())
return String.unsafeFromCharArray(buf)
}
private fun utoaDecSimple(buffer: CharArray, numInput: Int, offsetInput: Int) {
private fun utoaDecSimple(buffer: WasmCharArray, numInput: Int, offsetInput: Int) {
assert(numInput != 0)
assert(buffer.isNotEmpty())
assert(offsetInput > 0 && offsetInput <= buffer.size)
assert(buffer.len() > 0)
assert(offsetInput > 0 && offsetInput <= buffer.len())
var num = numInput
var offset = offsetInput
@@ -79,14 +79,14 @@ private fun utoaDecSimple(buffer: CharArray, numInput: Int, offsetInput: Int) {
val r = num % 10
num = t
offset--
buffer[offset] = digitToChar(r)
buffer.set(offset, digitToChar(r))
} while (num > 0)
}
private fun utoaDecSimple64(buffer: CharArray, numInput: Long, offsetInput: Int) {
private fun utoaDecSimple64(buffer: WasmCharArray, numInput: Long, offsetInput: Int) {
assert(numInput != 0L)
assert(buffer.isNotEmpty())
assert(offsetInput > 0 && offsetInput <= buffer.size)
assert(buffer.len() > 0)
assert(offsetInput > 0 && offsetInput <= buffer.len())
var num = numInput
var offset = offsetInput
@@ -95,7 +95,7 @@ private fun utoaDecSimple64(buffer: CharArray, numInput: Long, offsetInput: Int)
val r = (num % 10).toInt()
num = t
offset--
buffer[offset] = digitToChar(r)
buffer.set(offset, digitToChar(r))
} while (num > 0)
}
@@ -139,10 +139,10 @@ internal fun itoa64(inputValue: Long, radix: Int): String {
val absValue = if (sign == 1) -inputValue else inputValue
val decimals = decimalCount64High(absValue) + sign
val buf = CharArray(decimals)
val buf = WasmCharArray(decimals)
utoaDecSimple64(buf, absValue, decimals)
if (sign == 1)
buf[0] = CharCodes.MINUS.code.toChar()
buf.set(0, CharCodes.MINUS.code.toChar())
return String.unsafeFromCharArray(buf)
}
@@ -174,22 +174,21 @@ internal fun dtoa(value: Double): String {
return if (value < 0) "-Infinity" else "Infinity"
}
val buf = CharArray(MAX_DOUBLE_LENGTH)
val buf = WasmCharArray(MAX_DOUBLE_LENGTH)
val size = dtoaCore(buf, value)
val ret = CharArray(size)
val ret = WasmCharArray(size)
buf.copyInto(ret, 0, 0, size)
return String.unsafeFromCharArray(ret)
}
private fun dtoaCore(buffer: CharArray, valueInp: Double): Int {
private fun dtoaCore(buffer: WasmCharArray, valueInp: Double): Int {
var value = valueInp
val sign = (value < 0).toInt()
if (sign == 1) {
value = -value
buffer[0] = CharCodes.MINUS.code.toChar()
buffer.set(0, CharCodes.MINUS.code.toChar())
}
var len = grisu2(value, buffer, sign)
len = prettify(BufferWithOffset(buffer, sign), len - sign, _K)
return len + sign
@@ -242,7 +241,7 @@ private val FRC_POWERS = longArrayOf(
0x9E19DB92B4E31BA9UL.toLong(), 0xEB96BF6EBADF77D9UL.toLong(), 0xAF87023B9BF0EE6BUL.toLong()
)
private fun grisu2(value: Double, buffer: CharArray, sign: Int): Int {
private fun grisu2(value: Double, buffer: WasmCharArray, sign: Int): Int {
// frexp routine
val uv = value.toBits()
var exp = ((uv and 0x7FF0000000000000) ushr 52).toInt()
@@ -322,7 +321,7 @@ private fun getCachedPower(minExp: Int) {
_exp_pow = EXP_POWERS[index].toInt()
}
private fun genDigits(buffer: CharArray, w_frc: Long, w_exp: Int, mp_frc: Long, mp_exp: Int, deltaInp: Long, sign: Int): Int {
private fun genDigits(buffer: WasmCharArray, w_frc: Long, w_exp: Int, mp_frc: Long, mp_exp: Int, deltaInp: Long, sign: Int): Int {
var delta = deltaInp
val one_exp = -mp_exp
val one_frc = 1L shl one_exp
@@ -354,7 +353,7 @@ private fun genDigits(buffer: CharArray, w_frc: Long, w_exp: Int, mp_frc: Long,
}
if (d or len != 0)
buffer[len++] = digitToChar(d)
buffer.set(len++, digitToChar(d))
--kappa
val tmp = (p1.toLong() shl one_exp) + p2
@@ -373,7 +372,7 @@ private fun genDigits(buffer: CharArray, w_frc: Long, w_exp: Int, mp_frc: Long,
val d = p2 ushr one_exp
if (d or len.toLong() != 0L)
buffer[len++] = digitToChar(d.toInt())
buffer.set(len++, digitToChar(d.toInt()))
p2 = p2 and mask
--kappa
@@ -385,10 +384,10 @@ private fun genDigits(buffer: CharArray, w_frc: Long, w_exp: Int, mp_frc: Long,
}
}
private fun grisuRound(buffer: CharArray, len: Int, delta: Long, restInp: Long, ten_kappa: Long, wp_w: Long) {
private fun grisuRound(buffer: WasmCharArray, len: Int, delta: Long, restInp: Long, ten_kappa: Long, wp_w: Long) {
var rest = restInp
val lastp = len - 1
var digit = buffer[lastp]
var digit = buffer.get(lastp)
while (
rest < wp_w &&
delta - rest >= ten_kappa && (
@@ -399,17 +398,38 @@ private fun grisuRound(buffer: CharArray, len: Int, delta: Long, restInp: Long,
--digit
rest += ten_kappa;
}
buffer[lastp] = digit
buffer.set(lastp, digit)
}
private class BufferWithOffset(val buf: CharArray, val off: Int) {
private fun WasmCharArray.copyInto(destination: WasmCharArray, destinationOffset: Int, sourceOffset: Int, len: Int) {
var srcIndex: Int
var dstIndex: Int
var increment: Int
if (destinationOffset <= sourceOffset) {
srcIndex = sourceOffset
dstIndex = destinationOffset
increment = 1
} else {
srcIndex = sourceOffset + len - 1
dstIndex = destinationOffset + len - 1
increment = -1
}
repeat(len) {
destination.set(dstIndex, this.get(srcIndex))
srcIndex += increment
dstIndex += increment
}
}
private class BufferWithOffset(val buf: WasmCharArray, val off: Int) {
operator fun set(addr: Int, value: Char) {
buf[off + addr] = value
buf.set(off + addr, value)
}
fun memoryCopy(destAddr: Int, srcAddr: Int, len: Int) {
val startIdx = off + srcAddr
buf.copyInto(buf, off + destAddr, startIdx, startIdx + len)
buf.copyInto(buf, off + destAddr, startIdx, len)
}
fun offsetABitMore(anotherOff: Int) = BufferWithOffset(buf, off + anotherOff)
@@ -10,12 +10,10 @@ internal const val INT_SIZE_BYTES = 4
internal fun unsafeRawMemoryToChar(addr: Int) = wasm_i32_load16_u(addr).toChar()
internal fun unsafeRawMemoryToCharArray(startAddr: Int, length: Int): CharArray {
val ret = CharArray(length)
for (i in 0 until length) {
ret[i] = unsafeRawMemoryToChar(startAddr + i * CHAR_SIZE_BYTES)
}
return ret
internal fun unsafeRawMemoryToWasmCharArray(startAddr: Int, length: Int): WasmCharArray {
val result = WasmCharArray(length)
result.fill(length) { unsafeRawMemoryToChar(startAddr + it * CHAR_SIZE_BYTES) }
return result
}
// Returns a pointer into a temporary scratch segment in the raw wasm memory. Aligned by 4.
@@ -25,11 +23,13 @@ internal fun unsafeGetScratchRawMemory(sizeBytes: Int): Int =
implementedAsIntrinsic
// Assumes there is enough space at the destination, fails with wasm trap otherwise.
internal fun unsafeCharArrayToRawMemory(src: CharArray, dstAddr: Int) {
internal fun unsafeWasmCharArrayToRawMemory(src: WasmCharArray, dstAddr: Int) {
var curAddr = dstAddr
for (i in src) {
wasm_i32_store16(curAddr, i)
var i = 0
while (i < src.len()) {
wasm_i32_store16(curAddr, src.get(i))
curAddr += CHAR_SIZE_BYTES
i++
}
}
@@ -5,6 +5,8 @@
package kotlin.text
import kotlin.wasm.internal.*
internal fun insertString(array: CharArray, distIndex: Int, value: String, sourceIndex: Int, count: Int): Int {
var arrayIdx = distIndex
var stringIdx = sourceIndex
@@ -14,8 +16,11 @@ internal fun insertString(array: CharArray, distIndex: Int, value: String, sourc
return count
}
internal fun unsafeStringFromCharArray(array: CharArray, start: Int, size: Int): String =
String.unsafeFromCharArray(array.copyOfRange(start, start + size))
internal fun unsafeStringFromCharArray(array: CharArray, start: Int, size: Int): String {
val copy = WasmCharArray(size)
copy.fill(size) { array[it + start] }
return String.unsafeFromCharArray(copy)
}
internal fun insertInt(array: CharArray, start: Int, value: Int): Int {
val valueString = value.toString()
@@ -5,6 +5,8 @@
package kotlin.text
import kotlin.wasm.internal.*
/**
* Returns the index within this string of the first occurrence of the specified character, starting from the specified offset.
*/
@@ -70,7 +72,10 @@ public actual fun String(chars: CharArray): String =
public actual fun String(chars: CharArray, offset: Int, length: Int): String {
if (offset < 0 || length < 0 || offset + length > chars.size)
throw IndexOutOfBoundsException()
return String.unsafeFromCharArray(chars.copyOfRange(offset, offset + length))
val copy = WasmCharArray(length)
copy.fill(length) { chars[it + offset] }
return String.unsafeFromCharArray(copy)
}
/**
@@ -78,8 +83,11 @@ public actual fun String(chars: CharArray, offset: Int, length: Int): String {
*/
@SinceKotlin("1.4")
@WasExperimental(ExperimentalStdlibApi::class)
public actual fun CharArray.concatToString(): String =
String.unsafeFromCharArray(this.copyOf())
public actual fun CharArray.concatToString(): String {
val copy = WasmCharArray(this.size)
copy.fill(this.size) { this[it] }
return String.unsafeFromCharArray(copy)
}
/**
* Concatenates characters in this [CharArray] or its subrange into a String.
@@ -95,7 +103,11 @@ public actual fun CharArray.concatToString(): String =
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun CharArray.concatToString(startIndex: Int = 0, endIndex: Int = this.size): String {
AbstractList.checkBoundsIndexes(startIndex, endIndex, this.size)
return String.unsafeFromCharArray(this.copyOfRange(startIndex, endIndex))
val length = endIndex - startIndex
val copy = WasmCharArray(length)
copy.fill(length) { this[it + startIndex] }
return String.unsafeFromCharArray(copy)
}
/**