[WASM] POC stringref
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
index bb9797d..66b6d7a 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
@@ -142,7 +142,7 @@
context.irBuiltIns.floatType to getInternalFunction("consumeFloatIntoVoid"),
context.irBuiltIns.doubleType to getInternalFunction("consumeDoubleIntoVoid")
)
-
+
fun findVoidConsumer(type: IrType): IrSimpleFunctionSymbol =
consumePrimitiveIntoVoid[type] ?: consumeAnyIntoVoid
@@ -236,6 +236,7 @@
val newJsArray = getInternalFunction("newJsArray")
val jsArrayPush = getInternalFunction("jsArrayPush")
+ val jsArrayPushString = getInternalFunction("jsArrayPushString")
val startCoroutineUninterceptedOrReturnIntrinsics =
(0..2).map { getInternalFunction("startCoroutineUninterceptedOrReturnIntrinsic$it") }
@@ -304,6 +305,9 @@
private val wasmStructRefClass = getIrClass(FqName("kotlin.wasm.internal.reftypes.structref"))
val wasmStructRefType by lazy { wasmStructRefClass.defaultType }
+ private val wasmStringRefClass = getIrClass(FqName("kotlin.wasm.internal.reftypes.stringref"))
+ val wasmStringRefType by lazy { wasmStringRefClass.defaultType }
+
val wasmAnyRefClass = getIrClass(FqName("kotlin.wasm.internal.reftypes.anyref"))
private val jsAnyClass = getIrClass(FqName("kotlin.js.JsAny"))
@@ -385,4 +389,4 @@
fun getKFunctionType(type: IrType, list: List<IrType>): IrType {
return irBuiltIns.functionN(list.size).typeWith(list + type)
}
-}
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
index ecde31b..531be39 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
@@ -137,7 +137,7 @@
check(wasmArrayType != null)
val location = expression.getSourceLocation()
- generateAnyParameters(arrayClass.symbol, location)
+ generateAnyParameters(context, body, arrayClass.symbol, location)
if (!tryGenerateConstVarargArray(expression, wasmArrayType)) tryGenerateVarargArray(expression, wasmArrayType)
body.buildStructNew(context.referenceGcType(expression.type.getRuntimeClass(irBuiltIns).symbol), location)
}
@@ -294,7 +294,7 @@
}
if (expression.symbol.owner.hasWasmPrimitiveConstructorAnnotation()) {
- generateAnyParameters(klassSymbol, location)
+ generateAnyParameters(context, body, klassSymbol, location)
for (i in 0 until expression.valueArgumentsCount) {
generateExpression(expression.getValueArgument(i)!!)
}
@@ -307,21 +307,6 @@
generateCall(expression)
}
- private fun generateAnyParameters(klassSymbol: IrClassSymbol, location: SourceLocation) {
- //ClassITable and VTable load
- body.commentGroupStart { "Any parameters" }
- body.buildGetGlobal(context.referenceGlobalVTable(klassSymbol), location)
- if (klassSymbol.owner.hasInterfaceSuperClass()) {
- body.buildGetGlobal(context.referenceGlobalClassITable(klassSymbol), location)
- } else {
- body.buildRefNull(WasmHeapType.Simple.Struct, location)
- }
-
- body.buildConstI32Symbol(context.referenceTypeId(klassSymbol), location)
- body.buildConstI32(0, location) // Any::_hashCode
- body.commentGroupEnd()
- }
-
fun generateObjectCreationPrefixIfNeeded(constructor: IrConstructor) {
val parentClass = constructor.parentAsClass
if (constructor.origin == PrimaryConstructorLowering.SYNTHETIC_PRIMARY_CONSTRUCTOR) return
@@ -332,7 +317,7 @@
body.buildGetLocal(thisParameter, location)
body.buildInstr(WasmOp.REF_IS_NULL, location)
body.buildIf("this_init")
- generateAnyParameters(parentClass.symbol, location)
+ generateAnyParameters(context, body, parentClass.symbol, location)
val irFields: List<IrField> = parentClass.allFields(backendContext.irBuiltIns)
irFields.forEachIndexed { index, field ->
if (index > 1) {
@@ -362,7 +347,7 @@
private fun generateBox(expression: IrExpression, type: IrType) {
val klassSymbol = type.getRuntimeClass(irBuiltIns).symbol
val location = expression.getSourceLocation()
- generateAnyParameters(klassSymbol, location)
+ generateAnyParameters(context, body, klassSymbol, location)
generateExpression(expression)
body.buildStructNew(context.referenceGcType(klassSymbol), location)
body.commentPreviousInstr { "box" }
@@ -963,4 +948,21 @@
}
private fun IrElement.getSourceLocation() = getSourceLocation(functionContext.irFunction.fileOrNull?.fileEntry)
+
+ companion object {
+ fun generateAnyParameters(context: WasmModuleCodegenContext, body: WasmExpressionBuilder, klassSymbol: IrClassSymbol, location: SourceLocation) {
+ //ClassITable and VTable load
+ body.commentGroupStart { "Any parameters" }
+ body.buildGetGlobal(context.referenceGlobalVTable(klassSymbol), location)
+ if (klassSymbol.owner.hasInterfaceSuperClass()) {
+ body.buildGetGlobal(context.referenceGlobalClassITable(klassSymbol), location)
+ } else {
+ body.buildRefNull(WasmHeapType.Simple.Struct, location)
+ }
+
+ body.buildConstI32Symbol(context.referenceTypeId(klassSymbol), location)
+ body.buildConstI32(0, location) // Any::_hashCode
+ body.commentGroupEnd()
+ }
+ }
}
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
index ca28d41..dfb560c 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
@@ -490,6 +490,8 @@
is WasmRefNullExternrefType -> g.buildRefNull(WasmHeapType.Simple.NullNoExtern, location)
is WasmAnyRef -> g.buildRefNull(WasmHeapType.Simple.Any, location)
is WasmExternRef -> g.buildRefNull(WasmHeapType.Simple.Extern, location)
+ is WasmStringRef -> g.buildRefNull(WasmHeapType.Simple.StringRef, location)
+ is WasmStringViewWTF16 -> g.buildRefNull(WasmHeapType.Simple.StringViewWtf16, location)
WasmUnreachableType -> error("Unreachable type can't be initialized")
else -> error("Unknown value type ${type.name}")
}
@@ -535,4 +537,4 @@
body.commentGroupEnd()
}
else -> error("Unknown constant kind")
- }
+ }
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt
index bc48119..9df15c9 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt
@@ -104,6 +104,9 @@
"structref" -> WasmRefNullType(WasmHeapType.Simple.Struct)
"i31ref" -> WasmI31Ref
"funcref" -> WasmRefNullType(WasmHeapType.Simple.Func)
+ "stringref" -> WasmStringRef
+ "stringview_wtf16" -> WasmStringViewWTF16
+ "stringview_iter" -> WasmStringViewIter
else -> error("Unknown reference type $name")
}
} else if (ic != null) {
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
index 7e9e84e..d31cd32 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
@@ -195,5 +195,4 @@
fun addJsModuleImport(module: String) {
wasmFragment.jsModuleImports += module
}
-}
-
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt
index c0fa36c..7f54fde 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt
@@ -265,7 +265,6 @@
builtIns.doubleType,
context.wasmSymbols.voidType ->
return null
-
}
if (isExternalType(this))
@@ -858,7 +857,11 @@
),
this@irBlock
)
- +irCall(symbols.jsArrayPush).apply {
+ val jsArrayPush =
+ if (elementAdapter?.toType?.makeNotNull() != symbols.wasmStringRefType) symbols.jsArrayPush
+ else symbols.jsArrayPushString
+
+ +irCall(jsArrayPush).apply {
putValueArgument(0, irGet(newJsArrayVar))
putValueArgument(1, adaptedValue)
}
diff --git a/libraries/stdlib/wasm/builtins/kotlin/String.kt b/libraries/stdlib/wasm/builtins/kotlin/String.kt
index 85d08d8..72611da 100644
--- a/libraries/stdlib/wasm/builtins/kotlin/String.kt
+++ b/libraries/stdlib/wasm/builtins/kotlin/String.kt
@@ -1,32 +1,28 @@
/*
- * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin
import kotlin.wasm.internal.*
-import kotlin.math.min
+import kotlin.wasm.internal.reftypes.*
-/**
- * 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 internal @WasmPrimitiveConstructor constructor(
- private var leftIfInSum: String?,
- @kotlin.internal.IntrinsicConstEvaluation
- public override val length: Int,
- private var _chars: WasmCharArray,
-) : Comparable<String>, CharSequence {
+public class String internal @WasmPrimitiveConstructor constructor(internal val reference: stringref) : Comparable<String>, CharSequence {
public companion object {}
- /**
- * Returns a string obtained by concatenating this string with the string representation of the given [other] object.
- */
- @kotlin.internal.IntrinsicConstEvaluation
- public operator fun plus(other: Any?): String {
- val right = other.toString()
- return String(this, this.length + right.length, right.chars)
+ public override val length: Int get() =
+ wasm_stringview_wtf16_length(wasm_string_as_wtf16(reference))
+
+ public override fun get(index: Int): Char =
+ wasm_stringview_wtf16_get_codeunit(wasm_string_as_wtf16(reference), index).toChar()
+
+ public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
+ val view = wasm_string_as_wtf16(reference)
+ val length = wasm_stringview_wtf16_length(view)
+ val actualStartIndex = startIndex.coerceAtLeast(0)
+ val actualEndIndex = endIndex.coerceAtMost(length)
+ return String(wasm_stringview_wtf16_slice(view, actualStartIndex, actualEndIndex))
}
/**
@@ -35,99 +31,42 @@
* If the [index] is out of bounds of this string, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
- @kotlin.internal.IntrinsicConstEvaluation
- public override fun get(index: Int): Char {
- rangeCheck(index, this.length)
- return chars.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
+ public operator fun plus(other: Any?): String {
+ val otherReference = when (other) {
+ is String -> other.reference
+ else -> other.toString().reference
}
- check(currentStartIndex == 0)
- _chars = newArray
- leftIfInSum = null
+ return String(wasm_string_concat(reference, otherReference))
}
- internal inline val chars: WasmCharArray get() {
- if (leftIfInSum != null) {
- foldChars()
- }
- return _chars
- }
-
- public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
- val actualStartIndex = startIndex.coerceAtLeast(0)
- 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()
- }
-
- @kotlin.internal.IntrinsicConstEvaluation
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 minimumLength = if (thisLength < otherLength) thisLength else otherLength
+ val thisIterator = wasm_string_as_iter(this.reference)
+ val otherIterator = wasm_string_as_iter(other.reference)
- repeat(minimumLength) {
- val l = thisChars.get(it)
- val r = otherChars.get(it)
- if (l != r) return l - r
+ var thisCode = wasm_stringview_iter_next(thisIterator)
+ var otherCode = wasm_stringview_iter_next(otherIterator)
+ while (thisCode != -1 && otherCode != -1) {
+ val diff = thisCode - otherCode
+ if (diff != 0) return diff
+ thisCode = wasm_stringview_iter_next(thisIterator)
+ otherCode = wasm_stringview_iter_next(otherIterator)
}
- return thisLength - otherLength
+ return if (thisCode == -1 && otherCode == -1) 0 else this.length - other.length
}
- @kotlin.internal.IntrinsicConstEvaluation
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
+ if (other === null) return false
+ if (this === other) return true
+ return other is String && wasm_string_eq(reference, other.reference)
}
- @kotlin.internal.IntrinsicConstEvaluation
public override fun toString(): String = this
public override fun hashCode(): Int {
if (_hashCode != 0) return _hashCode
- val thisLength = this.length
- if (thisLength == 0) return 0
- val thisChars = chars
var hash = 0
- repeat(thisLength) {
- hash = (hash shl 5) - hash + thisChars.get(it).toInt()
- }
+ forEachCodePoint { hash = 31 * hash + it }
_hashCode = hash
return _hashCode
}
@@ -135,7 +74,16 @@
@Suppress("NOTHING_TO_INLINE")
internal inline fun WasmCharArray.createString(): String =
- String(null, this.len(), this)
+ String(wasm_string_new_wtf16_array(this, 0, len()))
+
+internal inline fun String.forEachCodePoint(body: (Int) -> Unit) {
+ val iter = wasm_string_as_iter(reference)
+ var codePoint = wasm_stringview_iter_next(iter)
+ while (codePoint != -1) {
+ body(codePoint)
+ codePoint = wasm_stringview_iter_next(iter)
+ }
+}
internal fun stringLiteral(poolId: Int, startAddress: Int, length: Int): String {
val cached = stringPool[poolId]
@@ -144,7 +92,7 @@
}
val chars = array_new_data0<WasmCharArray>(startAddress, length)
- val newString = String(null, length, chars)
+ val newString = chars.createString()
stringPool[poolId] = newString
return newString
}
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/builtins/kotlin/Throwable.kt b/libraries/stdlib/wasm/builtins/kotlin/Throwable.kt
index ca47635..6468591 100644
--- a/libraries/stdlib/wasm/builtins/kotlin/Throwable.kt
+++ b/libraries/stdlib/wasm/builtins/kotlin/Throwable.kt
@@ -5,10 +5,6 @@
package kotlin
-import kotlin.wasm.internal.ExternalInterfaceType
-import kotlin.wasm.internal.getSimpleName
-import kotlin.wasm.internal.jsToKotlinStringAdapter
-
/**
* The base class for all errors and exceptions. Only instances of this class can be thrown or caught.
*
@@ -22,19 +18,7 @@
constructor() : this(null, null)
- internal val jsStack: ExternalInterfaceType = captureStackTrace()
-
- private var _stack: String? = null
- internal val stack: String
- get() {
- var value = _stack
- if (value == null) {
- value = jsToKotlinStringAdapter(jsStack).removePrefix("Error\n")
- _stack = value
- }
-
- return value
- }
+ internal val stack: String = captureStackTrace()
internal var suppressedExceptionsList: MutableList<Throwable>? = null
@@ -43,10 +27,11 @@
* followed by the exception message if it is not null.
*/
public override fun toString(): String {
- val s = getSimpleName(this.typeInfo)
+ val kClass = this::class
+ val s = kClass.simpleName ?: "Throwable"
return if (message != null) s + ": " + message.toString() else s
}
}
-private fun captureStackTrace(): ExternalInterfaceType =
- js("new Error().stack")
+private fun captureStackTrace(): String =
+ js("new Error().stack.replace(/^Error\\n/, '')")
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExceptionHelpers.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExceptionHelpers.kt
index 893899e..368fb60 100644
--- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExceptionHelpers.kt
+++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExceptionHelpers.kt
@@ -6,7 +6,7 @@
package kotlin.wasm.internal
@Suppress("UNUSED_PARAMETER") // TODO: Remove after bootstrap update
-private fun throwJsError(message: String?, wasmTypeName: String?, stack: ExternalInterfaceType): Nothing {
+private fun throwJsError(message: String?, wasmTypeName: String?, stack: String): Nothing {
js("""
const error = new Error();
error.message = message;
@@ -17,7 +17,7 @@
}
internal fun throwAsJsException(t: Throwable): Nothing {
- throwJsError(t.message, getSimpleName(t.typeInfo), t.jsStack)
+ throwJsError(t.message, getSimpleName(t.typeInfo), t.stack)
}
internal var isNotFirstWasmExportCall: Boolean = false
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt
index ebcf85c..f928e65 100644
--- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt
+++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt
@@ -10,6 +10,7 @@
import kotlin.wasm.internal.reftypes.anyref
import kotlin.wasm.unsafe.withScopedMemoryAllocator
import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
+import kotlin.wasm.internal.reftypes.stringref
internal typealias ExternalInterfaceType = JsAny
@@ -185,93 +186,11 @@
x.asWasmExternRef()
}
-internal fun stringLength(x: ExternalInterfaceType): Int =
- js("x.length")
-
-// kotlin string to js string export
-// TODO Uint16Array may work with byte endian different with Wasm (i.e. little endian)
-internal fun importStringFromWasm(address: Int, length: Int, prefix: ExternalInterfaceType?): JsString {
- js("""
- const mem16 = new Uint16Array(wasmExports.memory.buffer, address, length);
- const str = String.fromCharCode.apply(null, mem16);
- return (prefix == null) ? str : prefix + str;
- """)
-}
-
-internal fun kotlinToJsStringAdapter(x: String?): JsString? {
- // Using nullable String to represent default value
- // for parameters with default values
- if (x == null) return null
- if (x.isEmpty()) return jsEmptyString
-
- val srcArray = x.chars
- val stringLength = srcArray.len()
- val maxStringLength = STRING_INTEROP_MEM_BUFFER_SIZE / CHAR_SIZE_BYTES
-
- @OptIn(UnsafeWasmMemoryApi::class)
- withScopedMemoryAllocator { allocator ->
- val memBuffer = allocator.allocate(stringLength.coerceAtMost(maxStringLength) * CHAR_SIZE_BYTES).address.toInt()
-
- var result: ExternalInterfaceType? = null
- var srcStartIndex = 0
- while (srcStartIndex < stringLength - maxStringLength) {
- unsafeWasmCharArrayToRawMemory(srcArray, srcStartIndex, maxStringLength, memBuffer)
- result = importStringFromWasm(memBuffer, maxStringLength, result)
- srcStartIndex += maxStringLength
- }
-
- unsafeWasmCharArrayToRawMemory(srcArray, srcStartIndex, stringLength - srcStartIndex, memBuffer)
- return importStringFromWasm(memBuffer, stringLength - srcStartIndex, result)
- }
-}
-
internal fun jsCheckIsNullOrUndefinedAdapter(x: ExternalInterfaceType?): ExternalInterfaceType? =
x.takeIf { !isNullish(it) }
-// js string to kotlin string import
-// TODO Uint16Array may work with byte endian different with Wasm (i.e. little endian)
-internal fun jsExportStringToWasm(src: ExternalInterfaceType, srcOffset: Int, srcLength: Int, dstAddr: Int) {
- js("""
- const mem16 = new Uint16Array(wasmExports.memory.buffer, dstAddr, srcLength);
- let arrayIndex = 0;
- let srcIndex = srcOffset;
- while (arrayIndex < srcLength) {
- mem16.set([src.charCodeAt(srcIndex)], arrayIndex);
- srcIndex++;
- arrayIndex++;
- }
- """)
-}
-
private const val STRING_INTEROP_MEM_BUFFER_SIZE = 65_536 // 1 page 4KiB
-internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String {
- val stringLength = stringLength(x)
- val dstArray = WasmCharArray(stringLength)
- if (stringLength == 0) {
- return dstArray.createString()
- }
-
- @OptIn(UnsafeWasmMemoryApi::class)
- withScopedMemoryAllocator { allocator ->
- val maxStringLength = STRING_INTEROP_MEM_BUFFER_SIZE / CHAR_SIZE_BYTES
- val memBuffer = allocator.allocate(stringLength.coerceAtMost(maxStringLength) * CHAR_SIZE_BYTES).address.toInt()
-
- var srcStartIndex = 0
- while (srcStartIndex < stringLength - maxStringLength) {
- jsExportStringToWasm(x, srcStartIndex, maxStringLength, memBuffer)
- unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, maxStringLength, dstArray)
- srcStartIndex += maxStringLength
- }
-
- jsExportStringToWasm(x, srcStartIndex, stringLength - srcStartIndex, memBuffer)
- unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, stringLength - srcStartIndex, dstArray)
- }
-
- return dstArray.createString()
-}
-
-
private fun getJsEmptyString(): JsString =
js("''")
@@ -293,29 +212,8 @@
return value
}
-private var _jsTrue: JsBoolean? = null
-private val jsTrue: JsBoolean
- get() {
- var value = _jsTrue
- if (value == null) {
- value = getJsTrue()
- _jsTrue = value
- }
-
- return value
- }
-
-private var _jsFalse: JsBoolean? = null
-private val jsFalse: JsBoolean
- get() {
- var value = _jsFalse
- if (value == null) {
- value = getJsFalse()
- _jsFalse = value
- }
-
- return value
- }
+private val jsTrue by lazy(::getJsTrue)
+private val jsFalse by lazy(::getJsFalse)
internal fun numberToDoubleAdapter(x: Number): Double =
x.toDouble()
@@ -369,9 +267,18 @@
internal fun kotlinCharToExternRefAdapter(x: Char): ExternalInterfaceType =
intToExternref(x.toInt())
+internal fun kotlinToJsStringAdapter(x: String?): stringref? =
+ x?.reference
+
+internal fun jsToKotlinStringAdapter(x: stringref): String =
+ String(x)
+
internal fun newJsArray(): ExternalInterfaceType =
js("[]")
internal fun jsArrayPush(array: ExternalInterfaceType, element: ExternalInterfaceType) {
js("array.push(element);")
}
+
+internal fun jsArrayPushString(array: ExternalInterfaceType, element: stringref): Unit =
+ js("array.push(element)")
diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt
index a093b14..716efcb 100644
--- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt
+++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt
@@ -80,4 +80,4 @@
@ExcludedFromCodegen
internal fun <T> wasmGetTypeInfoData(): TypeInfoData =
- implementedAsIntrinsic
+ implementedAsIntrinsic
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt
index ab1fb81..5fbaa63 100644
--- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt
+++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt
@@ -9,6 +9,7 @@
package kotlin.wasm.internal
+import kotlin.wasm.internal.reftypes.*
import kotlin.wasm.internal.ExternalInterfaceType
@WasmOp(WasmOp.UNREACHABLE)
@@ -390,3 +391,80 @@
@WasmOp(WasmOp.REF_IS_NULL)
internal external fun wasm_externref_is_null(a: ExternalInterfaceType?): Boolean
+
+// stringref proposal
+
+internal fun wasm_string_new_wtf16(address: Int, codeunits: Int): stringref =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_NEW_WTF16_ARRAY)
+internal fun wasm_string_new_wtf16_array(codeunits: WasmCharArray, start: Int, end: Int): stringref =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_MEASURE_WTF16)
+internal fun wasm_string_measure_wtf16(string: stringref): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_MEASURE_UTF8)
+internal fun wasm_string_measure_utf8(string: stringref): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_MEASURE_WTF8)
+internal fun wasm_string_measure_wtf8(string: stringref): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_ENCODE_WTF16_ARRAY)
+internal fun wasm_string_encode_wtf16_array(string: stringref, array: WasmCharArray, start: Int): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_NEW_LOSSY_UTF8_ARRAY)
+internal fun wasm_string_new_lossy_utf8_array(codeunits: WasmByteArray, start: Int, end: Int): stringref =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_ENCODE_LOSSY_UTF8_ARRAY)
+internal fun wasm_string_encode_lossy_utf8_array(string: stringref, array: WasmByteArray, start: Int): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_CONCAT)
+internal fun wasm_string_concat(string1: stringref, string2: stringref): stringref =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_EQ)
+internal fun wasm_string_eq(string1: stringref, string2: stringref): Boolean =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_AS_WTF16)
+internal fun wasm_string_as_wtf16(string: stringref): stringview_wtf16 =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_WTF16_LENGTH)
+internal fun wasm_stringview_wtf16_length(stringView: stringview_wtf16): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_WTF16_GET_CODEUNIT)
+internal fun wasm_stringview_wtf16_get_codeunit(stringView: stringview_wtf16, pos: Int): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_WTF16_SLICE)
+internal fun wasm_stringview_wtf16_slice(stringView: stringview_wtf16, start: Int, end: Int): stringref =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRING_AS_ITER)
+internal fun wasm_string_as_iter(string: stringref): stringview_iter =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_ITER_NEXT)
+internal fun wasm_stringview_iter_next(view: stringview_iter): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_ITER_ADVANCE)
+internal fun wasm_stringview_iter_advance(view: stringview_iter, codepoints: Int): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_ITER_REWIND)
+internal fun wasm_stringview_iter_rewind(view: stringview_iter, codepoints: Int): Int =
+ implementedAsIntrinsic
+
+@WasmOp(WasmOp.STRINGVIEW_ITER_SLICE)
+internal fun wasm_stringview_iter_slice(view: stringview_iter, codepoints: Int): Int =
+ implementedAsIntrinsic
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt
index 6c75bb4..38a2425 100644
--- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt
+++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt
@@ -23,4 +23,7 @@
internal interface eqref : anyref
internal interface structref : eqref
internal interface i31ref : eqref
-internal interface funcref : anyref
\ No newline at end of file
+internal interface funcref : anyref
+internal interface stringref
+internal interface stringview_wtf16
+internal interface stringview_iter
diff --git a/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt b/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt
index 766593a..47fe837 100644
--- a/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt
+++ b/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt
@@ -255,6 +255,25 @@
const val BR_ON_CAST_FAIL = "BR_ON_CAST_FAIL"
const val EXTERN_INTERNALIZE = "EXTERN_INTERNALIZE"
const val EXTERN_EXTERNALIZE = "EXTERN_EXTERNALIZE"
+ const val STRING_CONST = "STRING_CONST"
+ const val STRING_CONCAT = "STRING_CONCAT"
+ const val STRING_EQ = "STRING_EQ"
+ const val STRING_AS_ITER = "STRING_AS_ITER"
+ const val STRING_NEW_WTF16_ARRAY = "STRING_NEW_WTF16_ARRAY"
+ const val STRING_MEASURE_UTF8 = "STRING_MEASURE_UTF8"
+ const val STRING_MEASURE_WTF8 = "STRING_MEASURE_WTF8"
+ const val STRING_MEASURE_WTF16 = "STRING_MEASURE_WTF16"
+ const val STRING_ENCODE_WTF16_ARRAY = "STRING_ENCODE_WTF16_ARRAY"
+ const val STRING_NEW_LOSSY_UTF8_ARRAY = "STRING_NEW_LOSSY_UTF8_ARRAY"
+ const val STRING_ENCODE_LOSSY_UTF8_ARRAY = "STRING_ENCODE_LOSSY_UTF8_ARRAY"
+ const val STRINGVIEW_ITER_NEXT = "STRINGVIEW_ITER_NEXT"
+ const val STRINGVIEW_ITER_ADVANCE = "STRINGVIEW_ITER_ADVANCE"
+ const val STRINGVIEW_ITER_REWIND = "STRINGVIEW_ITER_REWIND"
+ const val STRINGVIEW_ITER_SLICE = "STRINGVIEW_ITER_SLICE"
+ const val STRINGVIEW_AS_WTF16 = "STRINGVIEW_AS_WTF16"
+ const val STRINGVIEW_WTF16_LENGTH = "STRINGVIEW_WTF16_LENGTH"
+ const val STRINGVIEW_WTF16_GET_CODEUNIT = "STRINGVIEW_WTF16_GET_CODEUNIT"
+ const val STRINGVIEW_WTF16_SLICE = "STRINGVIEW_WTF16_SLICE"
const val GET_UNIT = "GET_UNIT"
const val PSEUDO_COMMENT_PREVIOUS_INSTR = "PSEUDO_COMMENT_PREVIOUS_INSTR"
const val PSEUDO_COMMENT_GROUP_START = "PSEUDO_COMMENT_GROUP_START"
diff --git a/libraries/stdlib/wasm/src/kotlin/js/JsString.kt b/libraries/stdlib/wasm/src/kotlin/js/JsString.kt
index 103eea1..fa8a20e 100644
--- a/libraries/stdlib/wasm/src/kotlin/js/JsString.kt
+++ b/libraries/stdlib/wasm/src/kotlin/js/JsString.kt
@@ -6,11 +6,13 @@
package kotlin.js
import kotlin.wasm.internal.JsPrimitive
-import kotlin.wasm.internal.kotlinToJsStringAdapter
+import kotlin.wasm.internal.reftypes.stringref
-/** JavaScript primitive string */
+///** JavaScript primitive string */
@JsPrimitive("string")
public external class JsString internal constructor() : JsAny
+private fun unsafeCastToJsString(@Suppress("UNUSED_PARAMETER") ref: stringref): JsString = js("ref")
+
public fun String.toJsString(): JsString =
- kotlinToJsStringAdapter(this)!!
\ No newline at end of file
+ unsafeCastToJsString(reference)
\ No newline at end of file
diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt
index e9afcaa..57fcc3a 100644
--- a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt
+++ b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt
@@ -8,7 +8,9 @@
import kotlin.wasm.internal.*
internal fun insertString(array: CharArray, destinationIndex: Int, value: String, sourceIndex: Int, count: Int): Int {
- copyWasmArray(value.chars, array.storage, sourceIndex, destinationIndex, count)
+ val valueArray = WasmCharArray(value.length)
+ wasm_string_encode_wtf16_array(value.reference, valueArray, 0)
+ copyWasmArray(valueArray, array.storage, sourceIndex, destinationIndex, count)
return count
}
diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt
index 753439f..295c197 100644
--- a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt
+++ b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt
@@ -118,11 +118,10 @@
@SinceKotlin("1.4")
@WasExperimental(ExperimentalStdlibApi::class)
public actual fun String.toCharArray(): CharArray {
- val thisChars = this.chars
- val thisLength = thisChars.len()
- val newArray = CharArray(thisLength)
- copyWasmArray(thisChars, newArray.storage, 0, 0, thisLength)
- return newArray
+ val thisLength = length
+ val newArray = WasmCharArray(thisLength)
+ wasm_string_encode_wtf16_array(reference, newArray, 0)
+ return CharArray(newArray)
}
/**
@@ -141,7 +140,11 @@
AbstractList.checkBoundsIndexes(startIndex, endIndex, length)
val newLength = endIndex - startIndex
val newArray = CharArray(newLength)
- copyWasmArray(this.chars, newArray.storage, startIndex, 0, newLength)
+
+ val thisArray = WasmCharArray(length)
+ wasm_string_encode_wtf16_array(reference, thisArray, 0)
+
+ copyWasmArray(thisArray, newArray.storage, startIndex, 0, newLength)
return newArray
}
diff --git a/libraries/stdlib/wasm/src/kotlin/text/utf8Encoding.kt b/libraries/stdlib/wasm/src/kotlin/text/utf8Encoding.kt
index b389955..9649150 100644
--- a/libraries/stdlib/wasm/src/kotlin/text/utf8Encoding.kt
+++ b/libraries/stdlib/wasm/src/kotlin/text/utf8Encoding.kt
@@ -5,150 +5,7 @@
package kotlin.text
-/** Returns the negative [size] if [throwOnMalformed] is false, throws [CharacterCodingException] otherwise. */
-private fun malformed(size: Int, index: Int, throwOnMalformed: Boolean): Int {
- if (throwOnMalformed) throw CharacterCodingException("Malformed sequence starting at ${index - 1}")
- return -size
-}
-
-/**
- * Returns code point corresponding to UTF-16 surrogate pair,
- * where the first of the pair is the [high] and the second is in the [string] at the [index].
- * Returns zero if the pair is malformed and [throwOnMalformed] is false.
- *
- * @throws CharacterCodingException if the pair is malformed and [throwOnMalformed] is true.
- */
-private fun codePointFromSurrogate(string: String, high: Int, index: Int, endIndex: Int, throwOnMalformed: Boolean): Int {
- if (high !in 0xD800..0xDBFF || index >= endIndex) {
- return malformed(0, index, throwOnMalformed)
- }
- val low = string[index].code
- if (low !in 0xDC00..0xDFFF) {
- return malformed(0, index, throwOnMalformed)
- }
- return 0x10000 + ((high and 0x3FF) shl 10) or (low and 0x3FF)
-}
-
-/**
- * Returns code point corresponding to UTF-8 sequence of two bytes,
- * where the first byte of the sequence is the [byte1] and the second byte is in the [bytes] array at the [index].
- * Returns zero if the sequence is malformed and [throwOnMalformed] is false.
- *
- * @throws CharacterCodingException if the sequence of two bytes is malformed and [throwOnMalformed] is true.
- */
-private fun codePointFrom2(bytes: ByteArray, byte1: Int, index: Int, endIndex: Int, throwOnMalformed: Boolean): Int {
- if (byte1 and 0x1E == 0 || index >= endIndex) {
- return malformed(0, index, throwOnMalformed)
- }
- val byte2 = bytes[index].toInt()
- if (byte2 and 0xC0 != 0x80) {
- return malformed(0, index, throwOnMalformed)
- }
- return (byte1 shl 6) xor byte2 xor 0xF80
-}
-
-/**
- * Returns code point corresponding to UTF-8 sequence of three bytes,
- * where the first byte of the sequence is the [byte1] and the others are in the [bytes] array starting from the [index].
- * Returns a non-positive value indicating number of bytes from [bytes] included in malformed sequence
- * if the sequence is malformed and [throwOnMalformed] is false.
- *
- * @throws CharacterCodingException if the sequence of three bytes is malformed and [throwOnMalformed] is true.
- */
-private fun codePointFrom3(bytes: ByteArray, byte1: Int, index: Int, endIndex: Int, throwOnMalformed: Boolean): Int {
- if (index >= endIndex) {
- return malformed(0, index, throwOnMalformed)
- }
-
- val byte2 = bytes[index].toInt()
- if (byte1 and 0xF == 0) {
- if (byte2 and 0xE0 != 0xA0) {
- // Non-shortest form
- return malformed(0, index, throwOnMalformed)
- }
- } else if (byte1 and 0xF == 0xD) {
- if (byte2 and 0xE0 != 0x80) {
- // Surrogate code point
- return malformed(0, index, throwOnMalformed)
- }
- } else if (byte2 and 0xC0 != 0x80) {
- return malformed(0, index, throwOnMalformed)
- }
-
- if (index + 1 == endIndex) {
- return malformed(1, index, throwOnMalformed)
- }
- val byte3 = bytes[index + 1].toInt()
- if (byte3 and 0xC0 != 0x80) {
- return malformed(1, index, throwOnMalformed)
- }
-
- return (byte1 shl 12) xor (byte2 shl 6) xor byte3 xor -0x1E080
-}
-
-/**
- * Returns code point corresponding to UTF-8 sequence of four bytes,
- * where the first byte of the sequence is the [byte1] and the others are in the [bytes] array starting from the [index].
- * Returns a non-positive value indicating number of bytes from [bytes] included in malformed sequence
- * if the sequence is malformed and [throwOnMalformed] is false.
- *
- * @throws CharacterCodingException if the sequence of four bytes is malformed and [throwOnMalformed] is true.
- */
-private fun codePointFrom4(bytes: ByteArray, byte1: Int, index: Int, endIndex: Int, throwOnMalformed: Boolean): Int {
- if (index >= endIndex) {
- malformed(0, index, throwOnMalformed)
- }
-
- val byte2 = bytes[index].toInt()
- if (byte1 and 0xF == 0x0) {
- if (byte2 and 0xF0 <= 0x80) {
- // Non-shortest form
- return malformed(0, index, throwOnMalformed)
- }
- } else if (byte1 and 0xF == 0x4) {
- if (byte2 and 0xF0 != 0x80) {
- // Out of Unicode code points domain (larger than U+10FFFF)
- return malformed(0, index, throwOnMalformed)
- }
- } else if (byte1 and 0xF > 0x4) {
- return malformed(0, index, throwOnMalformed)
- } else if (byte2 and 0xC0 != 0x80) {
- return malformed(0, index, throwOnMalformed)
- }
-
- if (index + 1 == endIndex) {
- return malformed(1, index, throwOnMalformed)
- }
- val byte3 = bytes[index + 1].toInt()
- if (byte3 and 0xC0 != 0x80) {
- return malformed(1, index, throwOnMalformed)
- }
-
- if (index + 2 == endIndex) {
- return malformed(2, index, throwOnMalformed)
- }
- val byte4 = bytes[index + 2].toInt()
- if (byte4 and 0xC0 != 0x80) {
- return malformed(2, index, throwOnMalformed)
- }
- return (byte1 shl 18) xor (byte2 shl 12) xor (byte3 shl 6) xor byte4 xor 0x381F80
-}
-
-/**
- * Maximum number of bytes needed to encode a single char.
- *
- * Code points in `0..0x7F` are encoded in a single byte.
- * Code points in `0x80..0x7FF` are encoded in two bytes.
- * Code points in `0x800..0xD7FF` or in `0xE000..0xFFFF` are encoded in three bytes.
- * Surrogate code points in `0xD800..0xDFFF` are not Unicode scalar values, therefore aren't encoded.
- * Code points in `0x10000..0x10FFFF` are represented by a pair of surrogate `Char`s and are encoded in four bytes.
- */
-private const val MAX_BYTES_PER_CHAR = 3
-
-/**
- * The byte sequence a malformed UTF-16 char sequence is replaced by.
- */
-private val REPLACEMENT_BYTE_SEQUENCE: ByteArray = byteArrayOf(0xEF.toByte(), 0xBF.toByte(), 0xBD.toByte())
+import kotlin.wasm.internal.*
/**
* Encodes the [string] using UTF-8 and returns the resulting [ByteArray].
@@ -163,42 +20,12 @@
internal fun encodeUtf8(string: String, startIndex: Int, endIndex: Int, throwOnMalformed: Boolean): ByteArray {
require(startIndex >= 0 && endIndex <= string.length && startIndex <= endIndex)
- val bytes = ByteArray((endIndex - startIndex) * MAX_BYTES_PER_CHAR)
- var byteIndex = 0
- var charIndex = startIndex
-
- while (charIndex < endIndex) {
- val code = string[charIndex++].code
- when {
- code < 0x80 ->
- bytes[byteIndex++] = code.toByte()
- code < 0x800 -> {
- bytes[byteIndex++] = ((code shr 6) or 0xC0).toByte()
- bytes[byteIndex++] = ((code and 0x3F) or 0x80).toByte()
- }
- code < 0xD800 || code >= 0xE000 -> {
- bytes[byteIndex++] = ((code shr 12) or 0xE0).toByte()
- bytes[byteIndex++] = (((code shr 6) and 0x3F) or 0x80).toByte()
- bytes[byteIndex++] = ((code and 0x3F) or 0x80).toByte()
- }
- else -> { // Surrogate char value
- val codePoint = codePointFromSurrogate(string, code, charIndex, endIndex, throwOnMalformed)
- if (codePoint <= 0) {
- bytes[byteIndex++] = REPLACEMENT_BYTE_SEQUENCE[0]
- bytes[byteIndex++] = REPLACEMENT_BYTE_SEQUENCE[1]
- bytes[byteIndex++] = REPLACEMENT_BYTE_SEQUENCE[2]
- } else {
- bytes[byteIndex++] = ((codePoint shr 18) or 0xF0).toByte()
- bytes[byteIndex++] = (((codePoint shr 12) and 0x3F) or 0x80).toByte()
- bytes[byteIndex++] = (((codePoint shr 6) and 0x3F) or 0x80).toByte()
- bytes[byteIndex++] = ((codePoint and 0x3F) or 0x80).toByte()
- charIndex++
- }
- }
- }
+ val slicedString = wasm_stringview_wtf16_slice(wasm_string_as_wtf16(string.reference), startIndex, endIndex)
+ val arraySize = if (throwOnMalformed) wasm_string_measure_utf8(slicedString) else wasm_string_measure_wtf8(slicedString)
+ if (arraySize == -1) throw CharacterCodingException("Malformed sequence")
+ return ByteArray(arraySize).also {
+ wasm_string_encode_lossy_utf8_array(slicedString, it.storage, 0)
}
-
- return if (bytes.size == byteIndex) bytes else bytes.copyOf(byteIndex)
}
/**
@@ -219,53 +46,9 @@
internal fun decodeUtf8(bytes: ByteArray, startIndex: Int, endIndex: Int, throwOnMalformed: Boolean): String {
require(startIndex >= 0 && endIndex <= bytes.size && startIndex <= endIndex)
- var byteIndex = startIndex
- val stringBuilder = StringBuilder()
-
- while (byteIndex < endIndex) {
- val byte = bytes[byteIndex++].toInt()
- when {
- byte >= 0 ->
- stringBuilder.append(byte.toChar())
- byte shr 5 == -2 -> {
- val code = codePointFrom2(bytes, byte, byteIndex, endIndex, throwOnMalformed)
- if (code <= 0) {
- stringBuilder.append(REPLACEMENT_CHAR)
- byteIndex += -code
- } else {
- stringBuilder.append(code.toChar())
- byteIndex += 1
- }
- }
- byte shr 4 == -2 -> {
- val code = codePointFrom3(bytes, byte, byteIndex, endIndex, throwOnMalformed)
- if (code <= 0) {
- stringBuilder.append(REPLACEMENT_CHAR)
- byteIndex += -code
- } else {
- stringBuilder.append(code.toChar())
- byteIndex += 2
- }
- }
- byte shr 3 == -2 -> {
- val code = codePointFrom4(bytes, byte, byteIndex, endIndex, throwOnMalformed)
- if (code <= 0) {
- stringBuilder.append(REPLACEMENT_CHAR)
- byteIndex += -code
- } else {
- val high = (code - 0x10000) shr 10 or 0xD800
- val low = (code and 0x3FF) or 0xDC00
- stringBuilder.append(high.toChar())
- stringBuilder.append(low.toChar())
- byteIndex += 3
- }
- }
- else -> {
- malformed(0, byteIndex, throwOnMalformed)
- stringBuilder.append(REPLACEMENT_CHAR)
- }
- }
+ val result = String(wasm_string_new_lossy_utf8_array(bytes.storage, startIndex, endIndex))
+ if (throwOnMalformed) {
+ result.forEachCodePoint { if (it == 0xFFFD) throw CharacterCodingException("Malformed sequence") }
}
-
- return stringBuilder.toString()
+ return result
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt
index 2fc9ea9..a33c444 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt
@@ -114,4 +114,5 @@
internal fun MutableList<String>.addWasmExperimentalArguments() {
add("--experimental-wasm-gc")
+ add("--experimental-wasm-stringref")
}
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt
index 8eb805c..8e92cad 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt
@@ -25,6 +25,7 @@
val exports: List<WasmExport<*>> = emptyList(),
val elements: List<WasmElement> = emptyList(),
val tags: List<WasmTag> = emptyList(),
+ val constantStrings: List<String> = emptyList(),
val startFunction: WasmFunction? = null,
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt
index d274135..fb6ad1c 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt
@@ -385,6 +385,29 @@
EXTERN_INTERNALIZE("extern.internalize", 0xfb70), // externref -> anyref
EXTERN_EXTERNALIZE("extern.externalize", 0xfb71), // anyref -> externref
+ // stringref proposal
+ STRING_CONST("string.const", 0xFB_82, CONST_I32),
+ STRING_CONCAT("string.concat", 0xFB_88),
+ STRING_EQ("string.eq", 0xFB_89),
+ STRING_AS_ITER("string.as_iter", 0xFB_A0),
+
+ STRING_NEW_WTF16_ARRAY("string.new_wtf16_array", 0xFB_B1),
+ STRING_MEASURE_UTF8("string.measure_utf8", 0xFB_83),
+ STRING_MEASURE_WTF8("string.measure_wtf8", 0xFB_84),
+ STRING_MEASURE_WTF16("string.measure_wtf16", 0xFB_85),
+ STRING_ENCODE_WTF16_ARRAY("encode_wtf16_array", 0xFB_B3),
+ STRING_NEW_LOSSY_UTF8_ARRAY("string.new_lossy_utf8_array", 0xFB_B4),
+ STRING_ENCODE_LOSSY_UTF8_ARRAY("string.encode_lossy_utf8_array", 0xFB_B6),
+
+ STRINGVIEW_ITER_NEXT("stringview_iter.next", 0xFB_A1),
+ STRINGVIEW_ITER_ADVANCE("stringview_iter.advance", 0xFB_A2),
+ STRINGVIEW_ITER_REWIND("stringview_iter.rewind", 0xFB_A3),
+ STRINGVIEW_ITER_SLICE("stringview_iter.slice", 0xFB_A4),
+ STRINGVIEW_AS_WTF16("string.as_wtf16", 0xFB_98),
+ STRINGVIEW_WTF16_LENGTH("stringview_wtf16.length", 0xFB_99),
+ STRINGVIEW_WTF16_GET_CODEUNIT("stringview_wtf16.get_codeunit", 0xFB_9A),
+ STRINGVIEW_WTF16_SLICE("stringview_wtf16.slice", 0xFB_9C),
+
// ============================================================
// Pseudo-instruction, just alias for a normal call. It's used to easily spot get_unit on the wasm level.
GET_UNIT("call", 0x10, FUNC_IDX),
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt
index 210560a..55276cc 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt
@@ -27,6 +27,9 @@
object WasmEqRef : WasmType("eqref", -0x13)
object WasmRefNullNoneType : WasmType("nullnone", -0x1b)
object WasmRefNullExternrefType : WasmType("nullexternref", -0x17)
+object WasmStringRef : WasmType("stringref", -0x1c)
+object WasmStringViewWTF16 : WasmType("stringview_wtf16", -0x1e)
+object WasmStringViewIter : WasmType("stringview_iter", -0x1f)
data class WasmRefNullType(val heapType: WasmHeapType) : WasmType("ref null", -0x14)
data class WasmRefType(val heapType: WasmHeapType) : WasmType("ref", -0x15)
@@ -49,6 +52,12 @@
object Extern : Simple("extern", -0x11)
object Any : Simple("any", -0x12)
object Eq : Simple("eq", -0x13)
+ object StringRef : Simple("string", -0x1c)
+ object StringViewWtf16 : Simple("stringview", -0x1e)
+
+ @Suppress("unused")
+ object ExnH : Simple("exn", -0x18)
+
object Struct : Simple("struct", -0x19)
object NullNone : Simple("nullref", -0x1b)
object NullNoExtern : Simple("nullexternref", -0x17)
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt
index 1de08074..f0a4d58 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt
@@ -47,6 +47,10 @@
buildInstr(WasmOp.UNREACHABLE, location)
}
+ fun buildConstStringSymbol(value: WasmSymbol<Int>, location: SourceLocation) {
+ buildInstr(WasmOp.STRING_CONST, location, WasmImmediate.SymbolI32(value))
+ }
+
@Suppress("UNUSED_PARAMETER")
inline fun buildBlock(label: String?, resultType: WasmType? = null, body: (Int) -> Unit) {
numberOfNestedBlocks++
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToBinary.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToBinary.kt
index 3cd8df4..d029b93 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToBinary.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToBinary.kt
@@ -90,6 +90,12 @@
tags.forEach { appendTag(it) }
}
+ appendSection(14u) {
+ b.writeByte(0)
+ appendVectorSize(constantStrings.size)
+ constantStrings.forEach { appendConstString(it) }
+ }
+
appendSection(6u) {
appendVectorSize(globals.size)
globals.forEach { appendGlobal(it) }
@@ -216,8 +222,13 @@
return
if (opcode > 0xFF) {
- b.writeByte((opcode ushr 8).toByte())
- b.writeByte((opcode and 0xFF).toByte())
+ val prefix = opcode ushr 8
+ b.writeByte(prefix.toByte())
+ if (prefix == 0xFB) { //stringref proposal encodes instruction with uleb
+ b.writeVarUInt32((opcode and 0xFF).toUInt())
+ } else {
+ b.writeByte((opcode and 0xFF).toByte())
+ }
} else {
b.writeByte(opcode.toByte())
}
@@ -393,6 +404,12 @@
appendExpr(c.init)
}
+ private fun appendConstString(s: String) {
+ val utf8Array = s.toByteArray(Charsets.UTF_8)
+ appendVectorSize(utf8Array.size)
+ b.writeBytes(utf8Array)
+ }
+
private fun appendTag(t: WasmTag) {
if (t.importPair != null) {
b.writeString(t.importPair.moduleName)
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToText.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToText.kt
index 6e34930..3eaaad9 100644
--- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToText.kt
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmIrToText.kt
@@ -58,7 +58,7 @@
appendElement("align=$alignEffective")
}
- private fun appendInstr(wasmInstr: WasmInstr) {
+ private fun appendInstr(wasmInstr: WasmInstr, constantStrings: List<String>) {
val op = wasmInstr.operator
if (op.opcode == WASM_OP_PSEUDO_OPCODE) {
@@ -103,8 +103,13 @@
}
return
}
+
wasmInstr.immediates.forEach {
- appendImmediate(it)
+ if (wasmInstr.operator == WasmOp.STRING_CONST && it is WasmImmediate.SymbolI32) {
+ appendElement("\"${constantStrings[it.value.owner]}\"")
+ } else {
+ appendImmediate(it)
+ }
}
}
@@ -243,19 +248,19 @@
is WasmFunction.Imported -> appendImportedFunction(it)
is WasmMemory -> appendMemory(it)
is WasmTable -> appendTable(it)
- is WasmGlobal -> appendGlobal(it)
+ is WasmGlobal -> appendGlobal(it, constantStrings)
is WasmTag -> appendTag(it)
else -> error("Unknown import kind ${it::class}")
}
}
- definedFunctions.forEach { appendDefinedFunction(it) }
+ definedFunctions.forEach { appendDefinedFunction(it, constantStrings) }
tables.forEach { appendTable(it) }
memories.forEach { appendMemory(it) }
- globals.forEach { appendGlobal(it) }
+ globals.forEach { appendGlobal(it, constantStrings) }
exports.forEach { appendExport(it) }
- elements.forEach { appendWasmElement(it) }
+ elements.forEach { appendWasmElement(it, constantStrings) }
startFunction?.let { appendStartFunction(it) }
- data.forEach { appendData(it) }
+ data.forEach { appendData(it, constantStrings) }
tags.forEach { appendTag(it) }
}
}
@@ -327,7 +332,7 @@
}
}
- private fun appendDefinedFunction(function: WasmFunction.Defined) {
+ private fun appendDefinedFunction(function: WasmFunction.Defined, constantStrings: List<String>) {
newLineList("func") {
appendModuleFieldReference(function)
sameLineList("type") { appendModuleFieldReference(function.type) }
@@ -338,7 +343,7 @@
}
}
function.locals.forEach { if (!it.isParameter) appendLocal(it) }
- function.instructions.forEach { appendInstr(it) }
+ function.instructions.forEach { appendInstr(it, constantStrings) }
}
}
@@ -364,7 +369,7 @@
limits.maxSize?.let { appendElement(it.toString()) }
}
- private fun appendGlobal(global: WasmGlobal) {
+ private fun appendGlobal(global: WasmGlobal, constantStrings: List<String>) {
newLineList("global") {
appendModuleFieldReference(global)
@@ -375,7 +380,7 @@
else
appendType(global.type)
- global.init.forEach { appendInstr(it) }
+ global.init.forEach { appendInstr(it, constantStrings) }
}
}
@@ -388,7 +393,7 @@
}
}
- private fun appendWasmElement(element: WasmElement) {
+ private fun appendWasmElement(element: WasmElement, constantStrings: List<String>) {
newLineList("elem") {
when (val mode = element.mode) {
WasmElement.Mode.Passive -> {
@@ -397,7 +402,7 @@
if (mode.table.id != 0) {
sameLineList("table") { appendModuleFieldReference(mode.table) }
}
- sameLineList("") { appendInstr(mode.offset.single()) }
+ sameLineList("") { appendInstr(mode.offset.single(), constantStrings) }
}
WasmElement.Mode.Declarative -> {
appendElement("declare")
@@ -416,7 +421,7 @@
for (value in element.values) {
require(value is WasmTable.Value.Expression)
sameLineList("item") {
- appendInstr(value.expr.single())
+ appendInstr(value.expr.single(), constantStrings)
}
}
}
@@ -429,7 +434,7 @@
}
}
- private fun appendData(wasmData: WasmData) {
+ private fun appendData(wasmData: WasmData, constantStrings: List<String>) {
newLineList("data") {
when (val mode = wasmData.mode) {
is WasmDataMode.Active -> {
@@ -437,7 +442,7 @@
sameLineList("memory") { appendElement(mode.memoryIdx.toString()) }
}
sameLineList("") {
- appendInstr(mode.offset.single())
+ appendInstr(mode.offset.single(), constantStrings)
}
}
WasmDataMode.Passive -> {
diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/BasicWasmBoxTest.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/BasicWasmBoxTest.kt
index 97f74e0..27e96d8 100644
--- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/BasicWasmBoxTest.kt
+++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/BasicWasmBoxTest.kt
@@ -299,7 +299,7 @@
val failsIn = InTextDirectivesUtils.findListWithPrefixes(testFile, "// WASM_FAILS_IN: ")
- val exceptions = listOf(WasmVM.V8, WasmVM.SpiderMonkey).mapNotNull map@{ vm ->
+ val exceptions = listOf(WasmVM.V8/*, WasmVM.SpiderMonkey*/).mapNotNull map@{ vm ->
try {
if (debugMode >= DebugMode.DEBUG) {
println(" ------ Run in ${vm.name}" + if (vm.shortName in failsIn) " (expected to fail)" else "")
diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt
index 28e2b96..fb3a025 100644
--- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt
+++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt
@@ -23,6 +23,7 @@
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?) {
tool.run(
"--experimental-wasm-gc",
+ "--experimental-wasm-stringref",
*jsFiles.toTypedArray(),
"--module",
entryMjs,