[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,