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
@@ -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
|
||||
}
|
||||
|
||||
+16
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+38
-2
@@ -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() {
|
||||
@@ -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())
|
||||
}
|
||||
+7
-1
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
+1
-3
@@ -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)
|
||||
|
||||
+3
@@ -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?
|
||||
|
||||
|
||||
|
||||
+33
-7
@@ -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()
|
||||
}
|
||||
+10
-1
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user