mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
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:
committed by
TeamCityServer
parent
9ccdffe8ad
commit
0f84525bdc
1
.idea/dictionaries/igor.xml
generated
1
.idea/dictionaries/igor.xml
generated
@@ -1,6 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="igor">
|
||||
<words>
|
||||
<w>addr</w>
|
||||
<w>descr</w>
|
||||
<w>exprs</w>
|
||||
</words>
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -145,5 +145,9 @@ abstract class WasmExpressionBuilder {
|
||||
fun buildRttCanon(decl: WasmSymbol<WasmTypeDeclaration>) {
|
||||
buildInstr(WasmOp.RTT_CANON, WasmImmediate.TypeIdx(decl))
|
||||
}
|
||||
|
||||
fun buildDrop() {
|
||||
buildInstr(WasmOp.DROP)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user