[WASM] Caching string literals in global pool
This commit is contained in:
+3
-1
@@ -63,7 +63,9 @@ class WasmBackendContext(
|
||||
|
||||
override val internalPackageFqn = FqName("kotlin.wasm")
|
||||
|
||||
private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.wasm.internal"))
|
||||
val kotlinWasmInternalPackageFqn = internalPackageFqn.child(Name.identifier("internal"))
|
||||
|
||||
private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, kotlinWasmInternalPackageFqn)
|
||||
// TODO: Merge with JS IR Backend context lazy file
|
||||
val internalPackageFragment by lazy {
|
||||
IrFileImpl(object : IrFileEntry {
|
||||
|
||||
@@ -178,6 +178,7 @@ class WasmSymbols(
|
||||
val unboxIntrinsic: IrSimpleFunctionSymbol = getInternalFunction("unboxIntrinsic")
|
||||
|
||||
val stringGetLiteral = getFunction("stringLiteral", builtInsPackage)
|
||||
val stringGetPoolSize = getInternalFunction("stringGetPoolSize")
|
||||
|
||||
val testFun = maybeGetFunction("test", kotlinTestPackage)
|
||||
val suiteFun = maybeGetFunction("suite", kotlinTestPackage)
|
||||
|
||||
+38
-26
@@ -44,6 +44,10 @@ class BodyGenerator(
|
||||
buildInstr(WasmOp.GET_UNIT, WasmImmediate.FuncIdx(context.referenceFunction(unitGetInstance.symbol)))
|
||||
}
|
||||
|
||||
private val anyConstructor by lazy {
|
||||
wasmSymbols.any.constructors.first { it.owner.valueParameters.isEmpty() }
|
||||
}
|
||||
|
||||
// Generates code for the given IR element. Leaves something on the stack unless expression was of the type Void.
|
||||
internal fun generateExpression(elem: IrElement) {
|
||||
assert(elem is IrExpression || elem is IrVariable) { "Unsupported statement kind" }
|
||||
@@ -187,12 +191,13 @@ class BodyGenerator(
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall) {
|
||||
val klass: IrClass = expression.symbol.owner.parentAsClass
|
||||
val klassSymbol: IrClassSymbol = klass.symbol
|
||||
|
||||
require(!backendContext.inlineClassesUtils.isClassInlineLike(klass)) {
|
||||
"All inline class constructor calls must be lowered to static function calls"
|
||||
}
|
||||
|
||||
val wasmGcType: WasmSymbol<WasmTypeDeclaration> = context.referenceGcType(klass.symbol)
|
||||
val wasmGcType: WasmSymbol<WasmTypeDeclaration> = context.referenceGcType(klassSymbol)
|
||||
|
||||
if (klass.getWasmArrayAnnotation() != null) {
|
||||
require(expression.valueArgumentsCount == 1) { "@WasmArrayOf constructs must have exactly one argument" }
|
||||
@@ -204,29 +209,40 @@ class BodyGenerator(
|
||||
return
|
||||
}
|
||||
|
||||
//ClassITable and VTable load
|
||||
body.buildGetGlobal(context.referenceGlobalVTable(klass.symbol))
|
||||
if (klass.hasInterfaceSuperClass()) {
|
||||
body.buildGetGlobal(context.referenceGlobalClassITable(klass.symbol))
|
||||
} else {
|
||||
body.buildRefNull(WasmHeapType.Simple.Data)
|
||||
generateAnyParameters(klassSymbol)
|
||||
|
||||
if (expression.symbol.owner.hasWasmPrimitiveConstructorAnnotation()) {
|
||||
for (i in 0 until expression.valueArgumentsCount) {
|
||||
generateExpression(expression.getValueArgument(i)!!)
|
||||
}
|
||||
body.buildStructNew(wasmGcType)
|
||||
return
|
||||
}
|
||||
|
||||
val wasmClassId = context.referenceClassId(klass.symbol)
|
||||
|
||||
val irFields: List<IrField> = klass.allFields(backendContext.irBuiltIns)
|
||||
|
||||
irFields.forEachIndexed { index, field ->
|
||||
if (index == 0)
|
||||
body.buildConstI32Symbol(wasmClassId)
|
||||
else
|
||||
if (index > 1) {
|
||||
generateDefaultInitializerForType(context.transformType(field.type), body)
|
||||
}
|
||||
}
|
||||
|
||||
body.buildStructNew(wasmGcType)
|
||||
generateCall(expression)
|
||||
}
|
||||
|
||||
private fun generateAnyParameters(klassSymbol: IrClassSymbol) {
|
||||
//ClassITable and VTable load
|
||||
body.buildGetGlobal(context.referenceGlobalVTable(klassSymbol))
|
||||
if (klassSymbol.owner.hasInterfaceSuperClass()) {
|
||||
body.buildGetGlobal(context.referenceGlobalClassITable(klassSymbol))
|
||||
} else {
|
||||
body.buildRefNull(WasmHeapType.Simple.Data)
|
||||
}
|
||||
|
||||
body.buildConstI32Symbol(context.referenceClassId(klassSymbol))
|
||||
body.buildConstI32(0) // Any::_hashCode
|
||||
}
|
||||
|
||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) {
|
||||
val klass = context.irFunction.parentAsClass
|
||||
|
||||
@@ -244,19 +260,8 @@ class BodyGenerator(
|
||||
// Box intrinsic has an additional klass ID argument.
|
||||
// Processing it separately
|
||||
if (call.symbol == wasmSymbols.boxIntrinsic) {
|
||||
val klass = call.getTypeArgument(0)!!.getRuntimeClass(irBuiltIns)
|
||||
val klassSymbol = klass.symbol
|
||||
|
||||
//ClassITable and VTable load
|
||||
body.buildGetGlobal(context.referenceGlobalVTable(klassSymbol))
|
||||
if (klass.hasInterfaceSuperClass()) {
|
||||
body.buildGetGlobal(context.referenceGlobalClassITable(klassSymbol))
|
||||
} else {
|
||||
body.buildRefNull(WasmHeapType.Simple.Data)
|
||||
}
|
||||
|
||||
body.buildConstI32Symbol(context.referenceClassId(klassSymbol))
|
||||
body.buildConstI32(0) // Any::_hashCode
|
||||
val klassSymbol = call.getTypeArgument(0)!!.getRuntimeClass(irBuiltIns).symbol
|
||||
generateAnyParameters(klassSymbol)
|
||||
generateExpression(call.getValueArgument(0)!!)
|
||||
body.buildStructNew(context.referenceGcType(klassSymbol))
|
||||
return
|
||||
@@ -301,6 +306,9 @@ class BodyGenerator(
|
||||
return
|
||||
}
|
||||
|
||||
// We skip now calling any ctor because it is empty
|
||||
if (function.symbol.owner.hasWasmPrimitiveConstructorAnnotation()) return
|
||||
|
||||
val isSuperCall = call is IrCall && call.superQualifierSymbol != null
|
||||
if (function is IrSimpleFunction && function.isOverridable && !isSuperCall) {
|
||||
// Generating index for indirect call
|
||||
@@ -483,6 +491,10 @@ class BodyGenerator(
|
||||
body.buildInstr(WasmOp.ARRAY_COPY, immediate, immediate)
|
||||
}
|
||||
|
||||
wasmSymbols.stringGetPoolSize -> {
|
||||
body.buildConstI32Symbol(context.stringPoolSize)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
|
||||
+16
-6
@@ -372,13 +372,19 @@ class DeclarationGenerator(
|
||||
//TODO("FqName for inner classes could be invalid due to topping it out from outer class")
|
||||
val packageName = if (fqnShouldBeEmitted) classMetadata.klass.kotlinFqName.parentOrNull()?.asString() ?: "" else ""
|
||||
val simpleName = classMetadata.klass.kotlinFqName.shortName().asString()
|
||||
|
||||
val (packageNameAddress, packageNamePoolId) = context.referenceStringLiteralAddressAndId(packageName)
|
||||
val (simpleNameAddress, simpleNamePoolId) = context.referenceStringLiteralAddressAndId(simpleName)
|
||||
|
||||
val typeInfo = ConstantDataStruct(
|
||||
name = "TypeInfo",
|
||||
elements = listOf(
|
||||
ConstantDataIntField("TypePackageNameLength", packageName.length),
|
||||
ConstantDataIntField("TypePackageNamePtr", context.referenceStringLiteral(packageName)),
|
||||
ConstantDataIntField("TypePackageNameId", packageNamePoolId),
|
||||
ConstantDataIntField("TypePackageNamePtr", packageNameAddress),
|
||||
ConstantDataIntField("TypeNameLength", simpleName.length),
|
||||
ConstantDataIntField("TypeNamePtr", context.referenceStringLiteral(simpleName))
|
||||
ConstantDataIntField("TypeNameId", simpleNamePoolId),
|
||||
ConstantDataIntField("TypeNamePtr", simpleNameAddress)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -424,8 +430,8 @@ class DeclarationGenerator(
|
||||
|
||||
val initValue: IrExpression? = declaration.initializer?.expression
|
||||
if (initValue != null) {
|
||||
check(initValue is IrConst<*> && initValue.kind !is IrConstKind.String && initValue.kind !is IrConstKind.Null) {
|
||||
"Static field initializer should be non-string const or null"
|
||||
check(initValue is IrConst<*> && initValue.kind !is IrConstKind.String) {
|
||||
"Static field initializer should be string or const"
|
||||
}
|
||||
generateConstExpression(initValue, wasmExpressionGenerator, context)
|
||||
} else {
|
||||
@@ -481,8 +487,12 @@ fun generateConstExpression(expression: IrConst<*>, body: WasmExpressionBuilder,
|
||||
is IrConstKind.Float -> body.buildConstF32(kind.valueOf(expression))
|
||||
is IrConstKind.Double -> body.buildConstF64(kind.valueOf(expression))
|
||||
is IrConstKind.String -> {
|
||||
body.buildConstI32Symbol(context.referenceStringLiteral(kind.valueOf(expression)))
|
||||
body.buildConstI32(kind.valueOf(expression).length)
|
||||
val stringValue = kind.valueOf(expression)
|
||||
val (literalAddress, literalPoolId) = context.referenceStringLiteralAddressAndId(stringValue)
|
||||
body.buildConstI32Symbol(literalPoolId)
|
||||
body.buildConstI32Symbol(literalAddress)
|
||||
body.buildConstI32(stringValue.length)
|
||||
body.buildConstI32(stringValue.hashCode())
|
||||
body.buildCall(context.referenceFunction(context.backendContext.wasmSymbols.stringGetLiteral))
|
||||
}
|
||||
else -> error("Unknown constant kind")
|
||||
|
||||
+3
-1
@@ -18,6 +18,8 @@ interface WasmBaseCodegenContext {
|
||||
val scratchMemAddr: WasmSymbol<Int>
|
||||
val scratchMemSizeInBytes: Int
|
||||
|
||||
val stringPoolSize: WasmSymbol<Int>
|
||||
|
||||
fun referenceFunction(irFunction: IrFunctionSymbol): WasmSymbol<WasmFunction>
|
||||
fun referenceGlobalField(irField: IrFieldSymbol): WasmSymbol<WasmGlobal>
|
||||
fun referenceGlobalVTable(irClass: IrClassSymbol): WasmSymbol<WasmGlobal>
|
||||
@@ -34,7 +36,7 @@ interface WasmBaseCodegenContext {
|
||||
fun referenceClassId(irClass: IrClassSymbol): WasmSymbol<Int>
|
||||
fun referenceInterfaceId(irInterface: IrClassSymbol): WasmSymbol<Int>
|
||||
|
||||
fun referenceStringLiteral(string: String): WasmSymbol<Int>
|
||||
fun referenceStringLiteralAddressAndId(string: String): Pair<WasmSymbol<Int>, WasmSymbol<Int>>
|
||||
|
||||
fun transformType(irType: IrType): WasmType
|
||||
fun transformFieldType(irType: IrType): WasmType
|
||||
|
||||
+12
-7
@@ -36,7 +36,9 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
ReferencableElements<IrClassSymbol, Int>()
|
||||
val interfaceId =
|
||||
ReferencableElements<IrClassSymbol, Int>()
|
||||
val stringLiteralId =
|
||||
val stringLiteralAddress =
|
||||
ReferencableElements<String, Int>()
|
||||
val stringLiteralPoolId =
|
||||
ReferencableElements<String, Int>()
|
||||
|
||||
private val tagFuncType = WasmFunctionType(
|
||||
@@ -62,6 +64,8 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
val scratchMemAddr = WasmSymbol<Int>()
|
||||
val scratchMemSizeInBytes = 65_536
|
||||
|
||||
val stringPoolSize = WasmSymbol<Int>()
|
||||
|
||||
open class ReferencableElements<Ir, Wasm : Any> {
|
||||
val unbound = mutableMapOf<Ir, WasmSymbol<Wasm>>()
|
||||
fun reference(ir: Ir): WasmSymbol<Wasm> {
|
||||
@@ -92,7 +96,6 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
val wasmToIr = mutableMapOf<Wasm, Ir>()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
fun linkWasmCompiledFragments(): WasmModule {
|
||||
bind(functions.unbound, functions.defined)
|
||||
bind(globalFields.unbound, globalFields.defined)
|
||||
@@ -123,13 +126,16 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
|
||||
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())
|
||||
var stringLiteralCount = 0
|
||||
for ((string, symbol) in stringLiteralAddress.unbound) {
|
||||
val constData = ConstantDataCharArray("string_literal", string.toCharArray())
|
||||
stringDataSectionBytes += constData.toBytes().toList()
|
||||
stringAddrs[str] = currentDataSectionAddress
|
||||
symbol.bind(currentDataSectionAddress)
|
||||
stringLiteralPoolId.reference(string).bind(stringLiteralCount)
|
||||
currentDataSectionAddress += constData.sizeInBytes
|
||||
stringLiteralCount++
|
||||
}
|
||||
stringPoolSize.bind(stringLiteralCount)
|
||||
|
||||
// Reserve some memory to pass complex exported types (like strings). It's going to be accessible through 'unsafeGetScratchRawMemory'
|
||||
// runtime call from stdlib.
|
||||
@@ -138,7 +144,6 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
currentDataSectionAddress += scratchMemSizeInBytes
|
||||
|
||||
bind(classIds.unbound, klassIds)
|
||||
bind(stringLiteralId.unbound, stringAddrs)
|
||||
interfaceId.unbound.onEachIndexed { index, entry -> entry.value.bind(index) }
|
||||
|
||||
val data = typeInfo.buildData(address = { klassIds.getValue(it) }) +
|
||||
|
||||
+7
-2
@@ -25,6 +25,9 @@ class WasmModuleCodegenContextImpl(
|
||||
override val scratchMemAddr: WasmSymbol<Int>
|
||||
get() = wasmFragment.scratchMemAddr
|
||||
|
||||
override val stringPoolSize: WasmSymbol<Int>
|
||||
get() = wasmFragment.stringPoolSize
|
||||
|
||||
override val scratchMemSizeInBytes: Int
|
||||
get() = wasmFragment.scratchMemSizeInBytes
|
||||
|
||||
@@ -58,8 +61,10 @@ class WasmModuleCodegenContextImpl(
|
||||
return with(typeTransformer) { irType.toWasmBlockResultType() }
|
||||
}
|
||||
|
||||
override fun referenceStringLiteral(string: String): WasmSymbol<Int> {
|
||||
return wasmFragment.stringLiteralId.reference(string)
|
||||
override fun referenceStringLiteralAddressAndId(string: String): Pair<WasmSymbol<Int>, WasmSymbol<Int>> {
|
||||
val address = wasmFragment.stringLiteralAddress.reference(string)
|
||||
val id = wasmFragment.stringLiteralPoolId.reference(string)
|
||||
return address to id
|
||||
}
|
||||
|
||||
override fun generateTypeInfo(irClass: IrClassSymbol, typeInfo: ConstantDataElement) {
|
||||
|
||||
+15
-7
@@ -17,19 +17,20 @@ import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConstKind
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
/**
|
||||
* Move initialization of global fields to start function.
|
||||
*
|
||||
* WebAssembly allows only constant expressions to be used directly in
|
||||
* field initializers.
|
||||
*
|
||||
* TODO: Don't move null and nullable constant initializers
|
||||
* TODO: Make field initialization lazy. Needs design.
|
||||
*/
|
||||
class FieldInitializersLowering(val context: WasmBackendContext) : FileLoweringPass {
|
||||
private val stringPoolFqName = context.kotlinWasmInternalPackageFqn.child(Name.identifier("stringPool"))
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
val builder = context.createIrBuilder(context.fieldInitFunction.symbol)
|
||||
val startFunctionBody = context.fieldInitFunction.body as IrBlockBody
|
||||
@@ -48,11 +49,18 @@ class FieldInitializersLowering(val context: WasmBackendContext) : FileLoweringP
|
||||
if (!declaration.isStatic) return
|
||||
val initValue: IrExpression = declaration.initializer?.expression ?: return
|
||||
// Constant primitive initializers without implicit casting can be processed by native wasm initializers
|
||||
if (initValue.type == declaration.type && initValue is IrConst<*> && initValue.kind !is IrConstKind.String && initValue.kind !is IrConstKind.Null) return
|
||||
if (initValue is IrConst<*>) {
|
||||
if (initValue.kind is IrConstKind.Null) return
|
||||
if (initValue.type == declaration.type && initValue.kind !is IrConstKind.String) return
|
||||
}
|
||||
|
||||
val initializerStatement = builder.at(initValue).irSetField(null, declaration, initValue)
|
||||
|
||||
when (declaration.fqNameWhenAvailable) {
|
||||
stringPoolFqName -> startFunctionBody.statements.add(0, initializerStatement)
|
||||
else -> startFunctionBody.statements.add(initializerStatement)
|
||||
}
|
||||
|
||||
startFunctionBody.statements.add(
|
||||
builder.at(initValue).irSetField(null, declaration, initValue)
|
||||
)
|
||||
// Replace initializer with default one
|
||||
declaration.initializer = null
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ fun IrAnnotationContainer.hasWasmNoOpCastAnnotation(): Boolean =
|
||||
fun IrAnnotationContainer.hasWasmAutoboxedAnnotation(): Boolean =
|
||||
hasAnnotation(FqName("kotlin.wasm.internal.WasmAutoboxed"))
|
||||
|
||||
fun IrAnnotationContainer.hasWasmPrimitiveConstructorAnnotation(): Boolean =
|
||||
hasAnnotation(FqName("kotlin.wasm.internal.WasmPrimitiveConstructor"))
|
||||
|
||||
class WasmArrayInfo(val klass: IrClass, val isNullable: Boolean) {
|
||||
val type = klass.defaultType.let { if (isNullable) it.makeNullable() else it }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// IGNORE_BACKEND: WASM
|
||||
// WASM_MUTE_REASON: MINOR: CONST_EQUIVALENCE
|
||||
|
||||
object A {
|
||||
const val a: String = "$"
|
||||
const val b = "1234$a"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: WASM
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
Reference in New Issue
Block a user