[WASM] Caching string literals in global pool

This commit is contained in:
Igor Yakovlev
2022-10-07 19:03:04 +02:00
committed by teamcity
parent f95bfccf59
commit 033e2c45f1
21 changed files with 206 additions and 126 deletions
@@ -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)
@@ -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
}
@@ -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")
@@ -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
@@ -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) }) +
@@ -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) {
@@ -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 }
}
-3
View File
@@ -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