WASM: Implement wasm-native strings part 1

There are several changes here but they all required for at least one test to pass.
- Implemented String class and several utility functions using built-in CharArray
- Added new constant memory segment to hold string literals and required funcs to work with them
- Added very crude mostly incorrect rudimentary ability to pass strings back to javascript
This commit is contained in:
Igor Laevsky
2021-07-23 16:11:45 +03:00
committed by TeamCityServer
parent 9ccdffe8ad
commit 0f84525bdc
20 changed files with 229 additions and 84 deletions

View File

@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="igor">
<words>
<w>addr</w>
<w>descr</w>
<w>exprs</w>
</words>

View File

@@ -32,6 +32,9 @@ class WasmSymbols(
context.module.getPackage(FqName("kotlin.wasm.internal"))
private val collectionsPackage: PackageViewDescriptor =
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
private val builtInsPackage: PackageViewDescriptor =
context.module.getPackage(StandardNames.BUILT_INS_PACKAGE_FQ_NAME)
override val throwNullPointerException = getInternalFunction("THROW_NPE")
override val throwISE = getInternalFunction("THROW_ISE")
@@ -77,7 +80,7 @@ class WasmSymbols(
context.irBuiltIns.charType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.intType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.longType to getInternalFunction("wasm_i64_eq"),
context.irBuiltIns.stringType to getInternalFunction("wasm_string_eq")
context.irBuiltIns.stringType to getFunction("wasm_string_eq", builtInsPackage)
)
val floatEqualityFunctions = mapOf(
@@ -121,7 +124,7 @@ class WasmSymbols(
val boxIntrinsic: IrSimpleFunctionSymbol = getInternalFunction("boxIntrinsic")
val unboxIntrinsic: IrSimpleFunctionSymbol = getInternalFunction("unboxIntrinsic")
val stringGetLiteral = getInternalFunction("stringLiteral")
val stringGetLiteral = getFunction("stringLiteral", builtInsPackage)
val wasmClassId = getInternalFunction("wasmClassId")
val wasmInterfaceId = getInternalFunction("wasmInterfaceId")
@@ -140,6 +143,9 @@ class WasmSymbols(
val wasmThrow = getInternalFunction("wasmThrow")
val exportStringRet = getInternalFunction("exportStringRet")
val unsafeGetScratchRawMemory = getInternalFunction("unsafeGetScratchRawMemory")
private val functionNInterfaces = (0..22).map { arity ->
getIrClass(FqName("kotlin.wasm.internal.Function$arity"))
}
@@ -173,10 +179,12 @@ class WasmSymbols(
internal fun getProperty(fqName: FqName): PropertyDescriptor =
findProperty(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
private fun getInternalFunction(name: String): IrSimpleFunctionSymbol {
val tmp = findFunctions(wasmInternalPackage.memberScope, Name.identifier(name)).single()
private fun getFunction(name: String, ownerPackage: PackageViewDescriptor): IrSimpleFunctionSymbol {
val tmp = findFunctions(ownerPackage.memberScope, Name.identifier(name)).single()
return symbolTable.referenceSimpleFunction(tmp)
}
private fun getInternalFunction(name: String) = getFunction(name, wasmInternalPackage)
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
}

View File

@@ -132,6 +132,15 @@ fun WasmCompiledModuleFragment.generateJs(): String {
println(value) {
console.log(">>> " + value)
},
importStringToJs(addr, wasmMem) {
const mem16 = new Uint16Array(wasmMem.buffer);
const mem32 = new Int32Array(wasmMem.buffer);
const len = mem32[addr / 4];
const str_start_addr = (addr + 4) / 2
const slice = mem16.slice(str_start_addr, str_start_addr + len);
return String.fromCharCode.apply(null, slice);
}
};
""".trimIndent()
@@ -139,5 +148,5 @@ fun WasmCompiledModuleFragment.generateJs(): String {
val jsCode =
"\nconst js_code = {${jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode }}};"
return runtime + generateStringLiteralsSupport(stringLiterals) + jsCode
return runtime + jsCode
}

View File

@@ -58,6 +58,7 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
is IrConstKind.Double -> body.buildConstF64(kind.valueOf(expression))
is IrConstKind.String -> {
body.buildConstI32Symbol(context.referenceStringLiteral(kind.valueOf(expression)))
body.buildConstI32(kind.valueOf(expression).length)
body.buildCall(context.referenceFunction(wasmSymbols.stringGetLiteral))
}
else -> error("Unknown constant kind")
@@ -313,6 +314,13 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
body.buildRefCast()
generateInstanceFieldAccess(field)
}
wasmSymbols.unsafeGetScratchRawMemory -> {
// TODO: This drops size of the allocated segment. Instead we can check that it's in bounds for better error messages.
body.buildDrop()
body.buildConstI32Symbol(context.scratchMemAddr)
}
else -> {
return false
}
@@ -358,6 +366,14 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
}
}
// Handle complex exported parameters.
// TODO: This should live as a separate lowering which creates separate shims for each exported function.
if (context.irFunction.isExported(context.backendContext) &&
expression.value.type == irBuiltIns.stringType) {
body.buildCall(context.referenceFunction(wasmSymbols.exportStringRet))
}
body.buildInstr(WasmOp.RETURN)
}

View File

@@ -9,6 +9,9 @@ import org.jetbrains.kotlin.wasm.ir.WasmSymbol
// Representation of constant data in Wasm memory
internal const val CHAR_SIZE_BYTES = 2
internal const val INT_SIZE_BYTES = 4
sealed class ConstantDataElement {
abstract val sizeInBytes: Int
abstract fun dump(indent: String = "", startAddress: Int = 0): String
@@ -18,6 +21,18 @@ sealed class ConstantDataElement {
private fun addressToString(address: Int): String =
address.toString().padEnd(6, ' ')
class ConstantDataCharField(val name: String, val value: WasmSymbol<Char>) : ConstantDataElement() {
constructor(name: String, value: Char) : this(name, WasmSymbol(value))
override fun toBytes(): ByteArray = value.owner.toLittleEndianBytes()
override fun dump(indent: String, startAddress: Int): String {
return "${addressToString(startAddress)}: $indent i32 : ${value.owner} ;; $name\n"
}
override val sizeInBytes: Int = CHAR_SIZE_BYTES
}
class ConstantDataIntField(val name: String, val value: WasmSymbol<Int>) : ConstantDataElement() {
constructor(name: String, value: Int) : this(name, WasmSymbol(value))
@@ -27,7 +42,7 @@ class ConstantDataIntField(val name: String, val value: WasmSymbol<Int>) : Const
return "${addressToString(startAddress)}: $indent i32 : ${value.owner} ;; $name\n"
}
override val sizeInBytes: Int = 4
override val sizeInBytes: Int = INT_SIZE_BYTES
}
class ConstantDataIntArray(val name: String, val value: List<WasmSymbol<Int>>) : ConstantDataElement() {
@@ -40,7 +55,24 @@ class ConstantDataIntArray(val name: String, val value: List<WasmSymbol<Int>>) :
return "${addressToString(startAddress)}: $indent i32[] : ${value.map { it.owner }.toIntArray().contentToString()} ;; $name\n"
}
override val sizeInBytes: Int = value.size * 4
override val sizeInBytes: Int = value.size * INT_SIZE_BYTES
}
class ConstantDataCharArray(val name: String, val value: List<WasmSymbol<Char>>) : ConstantDataElement() {
constructor(name: String, value: CharArray) : this(name, value.map { WasmSymbol(it) })
override fun toBytes(): ByteArray {
return value
.map { it.owner.toLittleEndianBytes() }
.fold(byteArrayOf(), ByteArray::plus)
}
override fun dump(indent: String, startAddress: Int): String {
if (value.isEmpty()) return ""
return "${addressToString(startAddress)}: $indent i16[] : ${value.map { it.owner }.toCharArray().contentToString()} ;; $name\n"
}
override val sizeInBytes: Int = value.size * CHAR_SIZE_BYTES
}
class ConstantDataStruct(val name: String, val elements: List<ConstantDataElement>) : ConstantDataElement() {
@@ -68,3 +100,7 @@ fun Int.toLittleEndianBytes(): ByteArray {
(this ushr (it * 8)).toByte()
}
}
fun Char.toLittleEndianBytes(): ByteArray {
return byteArrayOf((this.code and 0xFF).toByte(), (this.code and 0xFF00).toByte())
}

View File

@@ -66,6 +66,12 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
// Generate function type
val watName = declaration.fqNameWhenAvailable.toString()
val irParameters = declaration.getEffectiveValueParameters()
// TODO: Exported types should be transformed in a separate lowering by creating shim functions for each export.
val resultType =
if (declaration.isExported(context.backendContext))
context.transformExportedResultType(declaration.returnType)
else
context.transformResultType(declaration.returnType)
val wasmFunctionType =
WasmFunctionType(
name = watName,
@@ -78,7 +84,7 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
}
},
resultTypes = listOfNotNull(
context.transformResultType(declaration.returnType).let {
resultType.let {
if (importedName != null && it is WasmRefNullType) WasmEqRef else it
}
)

View File

@@ -82,9 +82,6 @@ class WasmTypeTransformer(
builtIns.doubleType ->
WasmF64
builtIns.stringType ->
WasmExternRef
builtIns.nothingNType ->
WasmExternRef
@@ -92,6 +89,7 @@ class WasmTypeTransformer(
builtIns.nothingType ->
WasmExternRef
// this also handles builtIns.stringType
else -> {
val klass = this.getClass()
val ic = context.backendContext.inlineClassesUtils.getInlinedClass(this)

View File

@@ -19,6 +19,8 @@ import org.jetbrains.kotlin.wasm.ir.*
interface WasmBaseCodegenContext {
val backendContext: WasmBackendContext
val scratchMemAddr: WasmSymbol<Int>
fun referenceFunction(irFunction: IrFunctionSymbol): WasmSymbol<WasmFunction>
fun referenceGlobal(irField: IrFieldSymbol): WasmSymbol<WasmGlobal>
fun referenceGcType(irClass: IrClassSymbol): WasmSymbol<WasmTypeDeclaration>
@@ -41,6 +43,7 @@ interface WasmBaseCodegenContext {
fun transformBoxedType(irType: IrType): WasmType
fun transformValueParameterType(irValueParameter: IrValueParameter): WasmType
fun transformResultType(irType: IrType): WasmType?
fun transformExportedResultType(irType: IrType): WasmType?
fun transformBlockResultType(irType: IrType): WasmType?

View File

@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.wasm.ir2wasm
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.wasm.lower.WasmSignature
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment
@@ -40,7 +41,6 @@ class WasmCompiledModuleFragment {
val interfaces = mutableListOf<IrClassSymbol>()
val virtualFunctions = mutableListOf<IrSimpleFunctionSymbol>()
val signatures = LinkedHashSet<WasmSignature>()
val stringLiterals = mutableListOf<String>()
val typeInfo =
ReferencableAndDefinable<IrClassSymbol, ConstantDataElement>()
@@ -73,6 +73,9 @@ class WasmCompiledModuleFragment {
var startFunction: WasmFunction? = null
val scratchMemAddr = WasmSymbol<Int>()
val scratchMemSizeInBytes = 65_536
open class ReferencableElements<Ir, Wasm : Any> {
val unbound = mutableMapOf<Ir, WasmSymbol<Wasm>>()
fun reference(ir: Ir): WasmSymbol<Wasm> {
@@ -127,12 +130,28 @@ class WasmCompiledModuleFragment {
currentDataSectionAddress += typeInfoElement.sizeInBytes
}
val stringDataSectionStart = currentDataSectionAddress
val stringDataSectionBytes = mutableListOf<Byte>()
val stringAddrs = mutableMapOf<String, Int>()
for (str in stringLiteralId.unbound.keys) {
val constData = ConstantDataCharArray("string_literal", str.toCharArray())
stringDataSectionBytes += constData.toBytes().toList()
stringAddrs[str] = currentDataSectionAddress
currentDataSectionAddress += constData.sizeInBytes
}
// Reserve some memory to pass complex exported types (like strings). It's going to be accessible through 'unsafeGetScratchRawMemory'
// runtime call from stdlib.
currentDataSectionAddress = alignUp(currentDataSectionAddress, INT_SIZE_BYTES)
scratchMemAddr.bind(currentDataSectionAddress)
currentDataSectionAddress += scratchMemSizeInBytes
bind(classIds.unbound, klassIds)
bind(referencedClassITableAddresses.unbound, interfaceTableAddresses)
bind(stringLiteralId.unbound, stringAddrs)
bindIndices(virtualFunctionId.unbound, virtualFunctions)
bindIndices(signatureId.unbound, signatures.toList())
bindIndices(interfaceId.unbound, interfaces)
bindIndices(stringLiteralId.unbound, stringLiterals)
val interfaceImplementationIds = mutableMapOf<InterfaceImplementation, Int>()
val numberOfInterfaceImpls = mutableMapOf<IrClassSymbol, Int>()
@@ -145,9 +164,9 @@ class WasmCompiledModuleFragment {
bind(referencedInterfaceImplementationId.unbound, interfaceImplementationIds)
bind(interfaceMethodTables.unbound, interfaceMethodTables.defined)
val data =
typeInfo.buildData(address = { klassIds.getValue(it) }) +
definedClassITableData.buildData(address = { interfaceTableAddresses.getValue(it) })
val data = typeInfo.buildData(address = { klassIds.getValue(it) }) +
definedClassITableData.buildData(address = { interfaceTableAddresses.getValue(it) }) +
WasmData(WasmDataMode.Active(0, stringDataSectionStart), stringDataSectionBytes.toByteArray())
val logTypeInfo = false
if (logTypeInfo) {
@@ -222,6 +241,9 @@ class WasmCompiledModuleFragment {
val memorySizeInPages = (typeInfoSize / 65_536) + 1
val memory = WasmMemory(WasmLimits(memorySizeInPages.toUInt(), memorySizeInPages.toUInt()))
// Need to export the memory in order to pass complex objects to the host language.
exports += WasmExport.Memory("memory", memory)
val importedFunctions = functions.elements.filterIsInstance<WasmFunction.Imported>()
// Sorting by depth for a valid init order
@@ -285,8 +307,12 @@ inline fun WasmCompiledModuleFragment.ReferencableAndDefinable<IrClassSymbol, Co
}
}
data class InterfaceImplementation(
val irInterface: IrClassSymbol,
val irClass: IrClassSymbol
)
fun alignUp(x: Int, alignment: Int): Int {
assert(alignment and (alignment - 1) == 0) { "power of 2 expected" }
return (x + alignment - 1) and (alignment - 1).inv()
}

View File

@@ -28,6 +28,9 @@ class WasmModuleCodegenContextImpl(
private val typeTransformer =
WasmTypeTransformer(this, backendContext.irBuiltIns)
override val scratchMemAddr: WasmSymbol<Int>
get() = wasmFragment.scratchMemAddr
override fun transformType(irType: IrType): WasmType {
return with(typeTransformer) { irType.toWasmValueType() }
}
@@ -54,12 +57,18 @@ class WasmModuleCodegenContextImpl(
return with(typeTransformer) { irType.toWasmResultType() }
}
override fun transformExportedResultType(irType: IrType): WasmType? {
// Exported strings are passed as pointers to the raw memory
if (irType == backendContext.irBuiltIns.stringType)
return WasmI32
return with(typeTransformer) { irType.toWasmResultType() }
}
override fun transformBlockResultType(irType: IrType): WasmType? {
return with(typeTransformer) { irType.toWasmBlockResultType() }
}
override fun referenceStringLiteral(string: String): WasmSymbol<Int> {
wasmFragment.stringLiterals.add(string)
return wasmFragment.stringLiteralId.reference(string)
}

View File

@@ -151,7 +151,7 @@ abstract class BasicWasmBoxTest(
const wasmModule = new WebAssembly.Module(wasmBinary);
const wasmInstance = new WebAssembly.Instance(wasmModule, { runtime, js_code });
const actualResult = wasmInstance.exports.$testFunction();
const actualResult = runtime.importStringToJs(wasmInstance.exports.$testFunction(), wasmInstance.exports.memory);
if (actualResult !== "OK")
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`;
""".trimIndent()

View File

@@ -154,7 +154,3 @@ public class Char private constructor(public val value: Char) : Comparable<Char>
public const val SIZE_BITS: Int = 16
}
}
@WasmImport("runtime", "Char_toString")
private fun charToString(c: Char): String = implementedAsIntrinsic

View File

@@ -341,7 +341,7 @@ public class Byte private constructor(public val value: Byte) : Number(), Compar
wasm_i32_eq(this.toInt(), other.toInt())
public override fun toString(): String =
byteToStringImpl(this)
TODO("Wasm: string coercion")
public override inline fun hashCode(): Int =
this.toInt()
@@ -352,10 +352,6 @@ public class Byte private constructor(public val value: Byte) : Number(), Compar
implementedAsIntrinsic
}
@WasmImport("runtime", "coerceToString")
private fun byteToStringImpl(byte: Byte): String =
implementedAsIntrinsic
/**
* Represents a 16-bit signed integer.
*/
@@ -684,7 +680,8 @@ public class Short private constructor(public val value: Short) : Number(), Comp
public override fun equals(other: Any?): Boolean =
other is Short && wasm_i32_eq(this.toInt(), other.toInt())
public override fun toString(): String = shortToStringImpl(this)
public override fun toString(): String =
TODO("Wasm: string coercion")
public override inline fun hashCode(): Int =
this.toInt()
@@ -695,9 +692,6 @@ public class Short private constructor(public val value: Short) : Number(), Comp
implementedAsIntrinsic
}
@WasmImport("runtime", "coerceToString")
private fun shortToStringImpl(x: Short): String = implementedAsIntrinsic
/**
* Represents a 32-bit signed integer.
*/
@@ -1066,7 +1060,7 @@ public class Int private constructor(val value: Int) : Number(), Comparable<Int>
other is Int && wasm_i32_eq(this, other)
public override fun toString(): String =
intToStringImpl(this)
TODO("Wasm: string coercion")
public override inline fun hashCode(): Int =
this
@@ -1092,10 +1086,6 @@ public class Int private constructor(val value: Int) : Number(), Comparable<Int>
implementedAsIntrinsic
}
@WasmImport("runtime", "coerceToString")
private fun intToStringImpl(x: Int): String =
implementedAsIntrinsic
/**
* Represents a 64-bit signed integer.
*/
@@ -1462,9 +1452,8 @@ public class Long private constructor(val value: Long) : Number(), Comparable<Lo
public override fun equals(other: Any?): Boolean =
other is Long && wasm_i64_eq(this, other)
// TODO: Implement proper Long.toString
public override fun toString(): String =
toDouble().toString()
TODO("Wasm: string coercion")
public override fun hashCode(): Int =
((this ushr 32) xor this).toInt()
@@ -1769,7 +1758,7 @@ public class Float private constructor(public val value: Float) : Number(), Comp
other is Float && this.equals(other)
public override fun toString(): String =
floatToStringImpl(this)
TODO("Wasm: string coercion")
public override inline fun hashCode(): Int =
bits()
@@ -1779,10 +1768,6 @@ public class Float private constructor(public val value: Float) : Number(), Comp
internal fun bits(): Int = implementedAsIntrinsic
}
@WasmImport("runtime", "coerceToString")
private fun floatToStringImpl(x: Float): String =
implementedAsIntrinsic
/**
* Represents a double-precision 64-bit IEEE 754 floating point number.
*/
@@ -2086,7 +2071,7 @@ public class Double private constructor(public val value: Double) : Number(), Co
other is Double && this.bits() == other.bits()
public override fun toString(): String =
doubleToStringImpl(this)
TODO("Wasm: string coercion")
public override inline fun hashCode(): Int = bits().hashCode()
@@ -2095,7 +2080,3 @@ public class Double private constructor(public val value: Double) : Number(), Co
internal fun bits(): Long =
implementedAsIntrinsic
}
@WasmImport("runtime", "coerceToString")
private fun doubleToStringImpl(x: Double): String =
implementedAsIntrinsic

View File

@@ -11,18 +11,17 @@ import kotlin.wasm.internal.*
* The `String` class represents character strings. All string literals in Kotlin programs, such as `"abc"`, are
* implemented as instances of this class.
*/
@WasmAutoboxed
public class String constructor(public val string: String) : Comparable<String>, CharSequence {
public class String internal constructor(internal val chars: CharArray) : Comparable<String>, CharSequence {
public companion object;
/**
* Returns a string obtained by concatenating this string with the string representation of the given [other] object.
*/
public operator fun plus(other: Any?): String =
stringPlusImpl(this, other.toString())
String(chars + other.toString().chars)
public override val length: Int
get() = stringLengthImpl(this)
get() = chars.size
/**
* Returns the character of this string at the specified [index].
@@ -30,43 +29,49 @@ public class String constructor(public val string: String) : Comparable<String>,
* If the [index] is out of bounds of this string, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
public override fun get(index: Int): Char =
stringGetCharImpl(this, index)
public override fun get(index: Int): Char = chars[index]
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
stringSubSequenceImpl(this, startIndex, endIndex)
TODO("todo")
public override fun compareTo(other: String): Int =
stringCompareToImpl(this, other)
TODO("todo")
public override fun equals(other: Any?): Boolean {
if (other is String)
return this.compareTo(other) == 0
return chars.contentEquals(other.chars)
//return this.compareTo(other) == 0
return false
}
public override fun toString(): String = this
// TODO: Implement
// TODO: this is not nice
public override fun hashCode(): Int = 10
}
@JsFun("(it, other) => it + String(other)")
private fun stringPlusImpl(it: String, other: String): String =
implementedAsIntrinsic
internal fun stringLiteral(startAddr: Int, length: Int) = String(unsafeRawMemoryToCharArray(startAddr, length))
@JsFun("(it) => it.length")
private fun stringLengthImpl(it: String): Int =
implementedAsIntrinsic
internal fun charToString(c: Char): String = String(charArrayOf(c))
@WasmImport("runtime", "String_getChar")
private fun stringGetCharImpl(it: String, index: Int): Char =
implementedAsIntrinsic
internal fun wasm_string_eq(x: String, y: String): Boolean = x.equals(y)
@WasmImport("runtime", "String_compareTo")
private fun stringCompareToImpl(it: String, other: String): Int =
implementedAsIntrinsic
@WasmImport("runtime", "String_subsequence")
private fun stringSubSequenceImpl(string: String, startIndex: Int, endIndex: Int): String =
implementedAsIntrinsic
//@JsFun("(it, other) => it + String(other)")
//private fun stringPlusImpl(it: String, other: String): String =
// implementedAsIntrinsic
//
//@JsFun("(it) => it.length")
//private fun stringLengthImpl(it: String): Int =
// implementedAsIntrinsic
//
//@WasmImport("runtime", "String_getChar")
//private fun stringGetCharImpl(it: String, index: Int): Char =
// implementedAsIntrinsic
//
//@WasmImport("runtime", "String_compareTo")
//private fun stringCompareToImpl(it: String, other: String): Int =
// implementedAsIntrinsic
//
//@WasmImport("runtime", "String_subsequence")
//private fun stringSubSequenceImpl(string: String, startIndex: Int, endIndex: Int): String =
// implementedAsIntrinsic

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2010-2021 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.wasm.internal
// This is called when exported function returns a string. It writes [i32 length, [i16 chars ...]] into a temporary raw memory area and
// returns pointer to the start of it.
// Note: currently there is a single temporary raw memory area so it's not possible to export more than one string at a time.
internal fun exportStringRet(src: String): Int {
val retAddr = unsafeGetScratchRawMemory(INT_SIZE_BYTES + src.length * CHAR_SIZE_BYTES)
wasm_i32_store(retAddr, src.length)
unsafeCharArrayToRawMemory(src.toCharArray(), retAddr + INT_SIZE_BYTES)
return retAddr
}
// See importStringToJs for the JS-side import for strings

View File

@@ -5,10 +5,34 @@
package kotlin.wasm.internal
@WasmImport("runtime", "String_getLiteral")
internal fun stringLiteral(index: Int): String =
internal const val CHAR_SIZE_BYTES = 2
internal const val INT_SIZE_BYTES = 4
internal fun unsafeRawMemoryToChar(addr: Int) = wasm_i32_load16_u(addr).toChar()
internal fun unsafeRawMemoryToCharArray(startAddr: Int, length: Int): CharArray {
val ret = CharArray(length)
for (i in 0 until length) {
ret[i] = unsafeRawMemoryToChar(startAddr + i * CHAR_SIZE_BYTES)
}
return ret
}
// Returns a pointer into a temporary scratch segment in the raw wasm memory. Aligned by 4.
// Note: currently there is single such segment for a whole wasm module, so use with care.
@ExcludedFromCodegen
internal fun unsafeGetScratchRawMemory(sizeBytes: Int): Int =
implementedAsIntrinsic
// Assumes there is enough space at the destination, fails with wasm trap otherwise.
internal fun unsafeCharArrayToRawMemory(src: CharArray, dstAddr: Int) {
var curAddr = dstAddr
for (i in src) {
wasm_i32_store16(curAddr, i)
curAddr += CHAR_SIZE_BYTES
}
}
@WasmReinterpret
internal fun unsafeNotNull(x: Any?): Any =
implementedAsIntrinsic

View File

@@ -309,3 +309,12 @@ public external fun wasm_i64_trunc_sat_f64_s(a: Double): Long
@WasmOp(WasmOp.I32_LOAD)
public external fun wasm_i32_load(x: Int): Int
@WasmOp(WasmOp.I32_LOAD16_U)
public external fun wasm_i32_load16_u(x: Int): Int
@WasmOp(WasmOp.I32_STORE)
public external fun wasm_i32_store(addr: Int, i: Int): Unit
@WasmOp(WasmOp.I32_STORE16)
public external fun wasm_i32_store16(addr: Int, c: Char): Unit

View File

@@ -12,7 +12,3 @@ fun wasm_i32_compareTo(x: Int, y: Int): Int =
fun wasm_i64_compareTo(x: Long, y: Long): Int =
wasm_i64_ge_s(x, y).toInt() - wasm_i64_le_s(x, y).toInt()
@WasmImport("runtime", "String_equals")
fun wasm_string_eq(x: String, y: String): Boolean =
implementedAsIntrinsic

View File

@@ -287,7 +287,7 @@ public actual fun CharArray.concatToString(startIndex: Int, endIndex: Int): Stri
*/
@SinceKotlin("1.3")
@ExperimentalStdlibApi
public actual fun String.toCharArray(): CharArray = TODO("Wasm stdlib: Text")
public actual fun String.toCharArray(): CharArray = this.chars
/**
* Returns a [CharArray] containing characters of this string or its substring.

View File

@@ -145,5 +145,9 @@ abstract class WasmExpressionBuilder {
fun buildRttCanon(decl: WasmSymbol<WasmTypeDeclaration>) {
buildInstr(WasmOp.RTT_CANON, WasmImmediate.TypeIdx(decl))
}
fun buildDrop() {
buildInstr(WasmOp.DROP)
}
}