[WASM] Caching string literals in global pool

This commit is contained in:
Igor Yakovlev
2022-10-07 19:03:04 +02:00
committed by teamcity
parent f95bfccf59
commit 033e2c45f1
21 changed files with 206 additions and 126 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import kotlin.random.*
/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
*/
public open class Any {
public open class Any @WasmPrimitiveConstructor constructor() {
// Pointer to runtime type info
// Initialized by a compiler
@Suppress("MUST_BE_INITIALIZED_OR_BE_ABSTRACT")
@@ -102,8 +102,11 @@ public class Char private constructor(public val value: Char) : Comparable<Char>
public inline fun toDouble(): Double =
this.toInt().toDouble()
override fun toString(): String =
String(WasmCharArray(1).also { it.set(0, this) })
override fun toString(): String {
val array = WasmCharArray(1)
array.set(0, this)
return array.createString()
}
override fun hashCode(): Int =
this.toInt().hashCode()
+57 -50
View File
@@ -12,30 +12,19 @@ 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(
public class String internal @WasmPrimitiveConstructor constructor(
private var leftIfInSum: String?,
public override val length: Int,
private var _chars: WasmCharArray,
) : Comparable<String>, 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 right = if (other is String) other else other.toString()
return String(this, right.chars)
}
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
val right = other.toString()
return String(this, this.length + right.length, right.chars)
}
/**
@@ -45,11 +34,8 @@ public class String private constructor(
* where the behavior is unspecified.
*/
public override fun get(index: Int): Char {
if (index < 0) throw IndexOutOfBoundsException()
val folded = chars
val length = folded.len()
if (index >= length) throw IndexOutOfBoundsException()
return folded.get(index)
rangeCheck(index, this.length)
return chars.get(index)
}
internal fun foldChars() {
@@ -79,59 +65,80 @@ public class String private constructor(
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
val actualStartIndex = startIndex.coerceAtLeast(0)
val thisChars = chars
val actualEndIndex = endIndex.coerceAtMost(thisChars.len())
val newCharsLen = actualEndIndex - actualStartIndex
val newChars = WasmCharArray(newCharsLen)
copyWasmArray(thisChars, newChars, actualStartIndex, 0, newCharsLen)
return String(newChars)
val actualEndIndex = endIndex.coerceAtMost(this.length)
val newLength = actualEndIndex - actualStartIndex
if (newLength <= 0) return ""
val newChars = WasmCharArray(newLength)
copyWasmArray(chars, newChars, actualStartIndex, 0, newLength)
return newChars.createString()
}
public override fun compareTo(other: String): Int {
if (this === other) return 0
val thisChars = this.chars
val otherChars = other.chars
val thisLength = thisChars.len()
val otherLength = otherChars.len()
val len = min(thisLength, otherLength)
val minimumLength = if (thisLength < otherLength) thisLength else otherLength
for (i in 0 until len) {
val l = thisChars.get(i)
val r = otherChars.get(i)
if (l != r)
return l - r
repeat(minimumLength) {
val l = thisChars.get(it)
val r = otherChars.get(it)
if (l != r) return l - r
}
return thisLength - otherLength
}
public override fun equals(other: Any?): Boolean =
other != null &&
other is String &&
(this.length == other.length) &&
this.compareTo(other) == 0
public override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other === this) return true
val otherString = other as? String ?: return false
val thisLength = this.length
val otherLength = otherString.length
if (thisLength != otherLength) return false
val thisHash = this._hashCode
val otherHash = other._hashCode
if (thisHash != otherHash && thisHash != 0 && otherHash != 0) return false
val thisChars = this.chars
val otherChars = other.chars
repeat(thisLength) {
if (thisChars.get(it) != otherChars.get(it)) return false
}
return true
}
public override fun toString(): String = this
public override fun hashCode(): Int {
if (_hashCode != 0) return _hashCode
val thisChars = chars
val thisLength = thisChars.len()
val thisLength = this.length
if (thisLength == 0) return 0
val thisChars = chars
var hash = 0
var i = 0
while (i < thisLength) {
hash = 31 * hash + thisChars.get(i).toInt()
i++
repeat(thisLength) {
hash = (hash shl 5) - hash + thisChars.get(it).toInt()
}
_hashCode = hash
return _hashCode
}
}
internal fun stringLiteral(startAddr: Int, length: Int): String {
val array = WasmCharArray(length)
unsafeRawMemoryToWasmCharArray(startAddr, 0, length, array)
return String(array)
}
internal inline fun WasmCharArray.createString(): String =
String(null, this.len(), this)
internal fun stringLiteral(poolId: Int, startAddress: Int, length: Int, hashCode: Int): String {
val cached = stringPool[poolId]
if (cached !== null) {
return cached
}
val chars = WasmCharArray(length)
unsafeRawMemoryToWasmCharArray(startAddress, 0, length, chars)
val newString = String(null, length, chars)
newString._hashCode = hashCode
stringPool[poolId] = newString
return newString
}
@@ -9,7 +9,7 @@ import kotlin.wasm.internal.reftypes.anyref
internal external interface ExternalInterfaceType
internal class JsExternalBox(val ref: ExternalInterfaceType) {
internal class JsExternalBox @WasmPrimitiveConstructor constructor(val ref: ExternalInterfaceType) {
override fun toString(): String =
externrefToString(ref)
@@ -20,8 +20,13 @@ internal class JsExternalBox(val ref: ExternalInterfaceType) {
false
}
override fun hashCode(): Int =
externrefHashCode(ref)
override fun hashCode(): Int {
var hashCode = _hashCode
if (hashCode != 0) return hashCode
hashCode = externrefHashCode(ref)
_hashCode = hashCode
return hashCode
}
}
//language=js
@@ -218,7 +223,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String {
val stringLength = stringLength(x)
val dstArray = WasmCharArray(stringLength)
if (stringLength == 0) {
return String(dstArray)
return dstArray.createString()
}
val maxStringLength = unsafeGetScratchRawMemorySize() / CHAR_SIZE_BYTES
@@ -233,7 +238,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String {
jsExportStringToWasm(x, srcStartIndex, stringLength - srcStartIndex, memBuffer)
unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, stringLength - srcStartIndex, dstArray)
return String(dstArray)
return dstArray.createString()
}
@@ -64,7 +64,7 @@ internal fun itoa32(inputValue: Int, radix: Int): String {
if (sign == 1)
buf.set(0, CharCodes.MINUS.code.toChar())
return String(buf)
return buf.createString()
}
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(buf)
return buf.createString()
}
// 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(ret)
return ret.createString()
}
private fun dtoaCore(buffer: WasmCharArray, valueInp: Double): Int {
@@ -80,4 +80,12 @@ internal class Void private constructor()
// This is the only way to introduce Void type.
@WasmOp(WasmOp.DROP)
internal fun consumeAnyIntoVoid(a: Any?): Void =
implementedAsIntrinsic
implementedAsIntrinsic
@ExcludedFromCodegen
internal fun stringGetPoolSize(): Int =
implementedAsIntrinsic
// This initializer is a special case in FieldInitializersLowering
@EagerInitialization
internal val stringPool: Array<String?> = arrayOfNulls(stringGetPoolSize())
@@ -10,9 +10,11 @@ package kotlin.wasm.internal
internal const val TYPE_INFO_ELEMENT_SIZE = 4
internal const val TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET = 0
internal const val TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_SUPER_TYPE_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_ITABLE_SIZE_OFFSET = TYPE_INFO_SUPER_TYPE_OFFSET + TYPE_INFO_ELEMENT_SIZE
internal const val TYPE_INFO_ITABLE_OFFSET = TYPE_INFO_ITABLE_SIZE_OFFSET + TYPE_INFO_ELEMENT_SIZE
@@ -21,11 +23,13 @@ internal class TypeInfoData(val typeId: Int, val isInterface: Boolean, val packa
internal fun getTypeInfoTypeDataByPtr(typeInfoPtr: Int): TypeInfoData {
val fqNameLength = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET)
val fqNameLengthPtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET)
val fqNameId = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET)
val fqNamePtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET)
val simpleNameLength = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET)
val simpleNameId = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET)
val simpleNamePtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET)
val packageName = stringLiteral(fqNameLengthPtr, fqNameLength)
val simpleName = stringLiteral(simpleNamePtr, simpleNameLength)
val packageName = stringLiteral(fqNameId, fqNamePtr, fqNameLength, 0)
val simpleName = stringLiteral(simpleNameId, simpleNamePtr, simpleNameLength, 0)
return TypeInfoData(typeInfoPtr, isInterface = false, packageName, simpleName)
}
@@ -48,4 +48,13 @@ internal annotation class WasmAutoboxed
@ExcludedFromCodegen
internal val implementedAsIntrinsic: Nothing
get() = null!!
get() = null!!
/**
* Indicates that annotated constructor is primitive
* i.e. has direct layout from it's parameters to object fields (except any's fields) and has no code
* In this case no need to call it during an object creation.
*/
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
internal annotation class WasmPrimitiveConstructor
@@ -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(copy)
return copy.createString()
}
internal fun insertInt(array: CharArray, start: Int, value: Int): Int {
@@ -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(copy)
return copy.createString()
}
/**
@@ -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(copy)
return copy.createString()
}
/**
@@ -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(copy)
return copy.createString()
}
/**