From 0f84525bdcd8f5504ce020d5b81f93628f0c540d Mon Sep 17 00:00:00 2001 From: Igor Laevsky Date: Fri, 23 Jul 2021 16:11:45 +0300 Subject: [PATCH] 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 --- .idea/dictionaries/igor.xml | 1 + .../kotlin/backend/wasm/WasmSymbols.kt | 16 ++++-- .../jetbrains/kotlin/backend/wasm/compiler.kt | 11 +++- .../backend/wasm/ir2wasm/BodyGenerator.kt | 16 ++++++ .../backend/wasm/ir2wasm/ConstantData.kt | 40 ++++++++++++- .../wasm/ir2wasm/DeclarationGenerator.kt | 8 ++- .../backend/wasm/ir2wasm/TypeTransformer.kt | 4 +- .../wasm/ir2wasm/WasmBaseCodegenContext.kt | 3 + .../ir2wasm/WasmCompiledModuleFragment.kt | 40 ++++++++++--- .../ir2wasm/WasmModuleCodegenContextImpl.kt | 11 +++- .../kotlin/js/test/BasicWasmBoxTest.kt | 2 +- libraries/stdlib/wasm/builtins/kotlin/Char.kt | 4 -- .../stdlib/wasm/builtins/kotlin/Primitives.kt | 33 +++-------- .../stdlib/wasm/builtins/kotlin/String.kt | 57 ++++++++++--------- .../kotlin/wasm/internal/ImportExport.kt | 18 ++++++ .../internal/kotlin/wasm/internal/Runtime.kt | 28 ++++++++- .../kotlin/wasm/internal/WasmInstructions.kt | 11 +++- .../internal/kotlin/wasm/internal/WasmMath.kt | 4 -- libraries/stdlib/wasm/src/kotlin/Text.kt | 2 +- .../kotlin/wasm/ir/WasmExpressionBuilder.kt | 4 ++ 20 files changed, 229 insertions(+), 84 deletions(-) create mode 100644 libraries/stdlib/wasm/internal/kotlin/wasm/internal/ImportExport.kt diff --git a/.idea/dictionaries/igor.xml b/.idea/dictionaries/igor.xml index 638ab197852..81b6ec5af81 100644 --- a/.idea/dictionaries/igor.xml +++ b/.idea/dictionaries/igor.xml @@ -1,6 +1,7 @@ + addr descr exprs 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 6babdd6b487..8907d73d696 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 @@ -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)) } \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index 43d44c9ef01..468bd78d35c 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -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 } 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 289a44c0451..970dfd30869 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 @@ -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) } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ConstantData.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ConstantData.kt index d427e77b11d..be67f2684a6 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ConstantData.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ConstantData.kt @@ -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) : 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) : ConstantDataElement() { constructor(name: String, value: Int) : this(name, WasmSymbol(value)) @@ -27,7 +42,7 @@ class ConstantDataIntField(val name: String, val value: WasmSymbol) : 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>) : ConstantDataElement() { @@ -40,7 +55,24 @@ class ConstantDataIntArray(val name: String, val value: List>) : 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>) : 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() { @@ -67,4 +99,8 @@ fun Int.toLittleEndianBytes(): ByteArray { return ByteArray(4) { (this ushr (it * 8)).toByte() } +} + +fun Char.toLittleEndianBytes(): ByteArray { + return byteArrayOf((this.code and 0xFF).toByte(), (this.code and 0xFF00).toByte()) } \ No newline at end of file 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 5da41f88be9..4bd065aafff 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 @@ -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 } ) 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 aa0302cd0e7..6aab0dddbf2 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 @@ -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) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt index 2cd7d108e8b..c8ee0e470bb 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt @@ -19,6 +19,8 @@ import org.jetbrains.kotlin.wasm.ir.* interface WasmBaseCodegenContext { val backendContext: WasmBackendContext + val scratchMemAddr: WasmSymbol + fun referenceFunction(irFunction: IrFunctionSymbol): WasmSymbol fun referenceGlobal(irField: IrFieldSymbol): WasmSymbol fun referenceGcType(irClass: IrClassSymbol): WasmSymbol @@ -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? diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt index 4b68bebfb14..1400b4c1108 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt @@ -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() val virtualFunctions = mutableListOf() val signatures = LinkedHashSet() - val stringLiterals = mutableListOf() val typeInfo = ReferencableAndDefinable() @@ -73,6 +73,9 @@ class WasmCompiledModuleFragment { var startFunction: WasmFunction? = null + val scratchMemAddr = WasmSymbol() + val scratchMemSizeInBytes = 65_536 + open class ReferencableElements { val unbound = mutableMapOf>() fun reference(ir: Ir): WasmSymbol { @@ -127,12 +130,28 @@ class WasmCompiledModuleFragment { currentDataSectionAddress += typeInfoElement.sizeInBytes } + val stringDataSectionStart = currentDataSectionAddress + val stringDataSectionBytes = mutableListOf() + val stringAddrs = mutableMapOf() + 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() val numberOfInterfaceImpls = mutableMapOf() @@ -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() // Sorting by depth for a valid init order @@ -285,8 +307,12 @@ inline fun WasmCompiledModuleFragment.ReferencableAndDefinable + 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 { - wasmFragment.stringLiterals.add(string) return wasmFragment.stringLiteralId.reference(string) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt index b154b597b80..1c66e8a0e3f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt @@ -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() diff --git a/libraries/stdlib/wasm/builtins/kotlin/Char.kt b/libraries/stdlib/wasm/builtins/kotlin/Char.kt index 0630878abde..5fe37924517 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Char.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Char.kt @@ -154,7 +154,3 @@ public class Char private constructor(public val value: Char) : Comparable public const val SIZE_BITS: Int = 16 } } - -@WasmImport("runtime", "Char_toString") -private fun charToString(c: Char): String = implementedAsIntrinsic - diff --git a/libraries/stdlib/wasm/builtins/kotlin/Primitives.kt b/libraries/stdlib/wasm/builtins/kotlin/Primitives.kt index d9dd298f8f1..0e669c47d5d 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Primitives.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Primitives.kt @@ -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 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 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, CharSequence { +public class String internal constructor(internal val chars: CharArray) : Comparable, 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, * 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 diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ImportExport.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ImportExport.kt new file mode 100644 index 00000000000..2970af3c64e --- /dev/null +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ImportExport.kt @@ -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 \ No newline at end of file diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt index 9046afa96f7..081bbddfd2e 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt @@ -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 diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt index 4b8d1fe7393..86f40ba1afc 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt @@ -308,4 +308,13 @@ public external fun wasm_i64_trunc_sat_f32_s(a: Float): Long 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 \ No newline at end of file +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 \ No newline at end of file diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmMath.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmMath.kt index 2a1c273fd23..a270e3c78c0 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmMath.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmMath.kt @@ -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 \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/Text.kt b/libraries/stdlib/wasm/src/kotlin/Text.kt index dd27761b0a9..4f2036e0664 100644 --- a/libraries/stdlib/wasm/src/kotlin/Text.kt +++ b/libraries/stdlib/wasm/src/kotlin/Text.kt @@ -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. 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 813f3d42c2c..b2f8cab421c 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 @@ -145,5 +145,9 @@ abstract class WasmExpressionBuilder { fun buildRttCanon(decl: WasmSymbol) { buildInstr(WasmOp.RTT_CANON, WasmImmediate.TypeIdx(decl)) } + + fun buildDrop() { + buildInstr(WasmOp.DROP) + } }