From 033e2c45f181d936ca52d4ff526c6419219ff6da Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Fri, 7 Oct 2022 19:03:04 +0200 Subject: [PATCH] [WASM] Caching string literals in global pool --- .../kotlin/backend/wasm/WasmBackendContext.kt | 4 +- .../kotlin/backend/wasm/WasmSymbols.kt | 1 + .../backend/wasm/ir2wasm/BodyGenerator.kt | 64 ++++++----- .../wasm/ir2wasm/DeclarationGenerator.kt | 22 +++- .../wasm/ir2wasm/WasmBaseCodegenContext.kt | 4 +- .../ir2wasm/WasmCompiledModuleFragment.kt | 19 ++-- .../ir2wasm/WasmModuleCodegenContextImpl.kt | 9 +- .../wasm/lower/FieldInitializersLowering.kt | 22 ++-- .../kotlin/backend/wasm/utils/Annotations.kt | 3 + .../testData/codegen/box/constants/kt9532.kt | 3 - .../boxInline/special/identityCheck.kt | 1 - libraries/stdlib/wasm/builtins/kotlin/Any.kt | 2 +- libraries/stdlib/wasm/builtins/kotlin/Char.kt | 7 +- .../stdlib/wasm/builtins/kotlin/String.kt | 107 ++++++++++-------- .../kotlin/wasm/internal/ExternalWrapper.kt | 15 ++- .../kotlin/wasm/internal/Number2String.kt | 6 +- .../internal/kotlin/wasm/internal/Runtime.kt | 10 +- .../internal/kotlin/wasm/internal/TypeInfo.kt | 14 ++- .../kotlin/wasm/internal/WasmAnnotations.kt | 11 +- .../wasm/src/kotlin/text/StringBuilderWasm.kt | 2 +- .../wasm/src/kotlin/text/StringsWasm.kt | 6 +- 21 files changed, 206 insertions(+), 126 deletions(-) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt index c72527c5837..13fc8914106 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt @@ -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 { 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 3e8a2e53717..3ecfe4feb18 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 @@ -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) 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 9b5f82ab7c2..b288ec11a4d 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 @@ -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 = context.referenceGcType(klass.symbol) + val wasmGcType: WasmSymbol = 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 = 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 } 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 92222fb761e..00fae05abef 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 @@ -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") 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 a2dd9197a08..2e732575a1c 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 @@ -18,6 +18,8 @@ interface WasmBaseCodegenContext { val scratchMemAddr: WasmSymbol val scratchMemSizeInBytes: Int + val stringPoolSize: WasmSymbol + fun referenceFunction(irFunction: IrFunctionSymbol): WasmSymbol fun referenceGlobalField(irField: IrFieldSymbol): WasmSymbol fun referenceGlobalVTable(irClass: IrClassSymbol): WasmSymbol @@ -34,7 +36,7 @@ interface WasmBaseCodegenContext { fun referenceClassId(irClass: IrClassSymbol): WasmSymbol fun referenceInterfaceId(irInterface: IrClassSymbol): WasmSymbol - fun referenceStringLiteral(string: String): WasmSymbol + fun referenceStringLiteralAddressAndId(string: String): Pair, WasmSymbol> fun transformType(irType: IrType): WasmType fun transformFieldType(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 da1b237105c..3fa04ab519f 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 @@ -36,7 +36,9 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { ReferencableElements() val interfaceId = ReferencableElements() - val stringLiteralId = + val stringLiteralAddress = + ReferencableElements() + val stringLiteralPoolId = ReferencableElements() private val tagFuncType = WasmFunctionType( @@ -62,6 +64,8 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { val scratchMemAddr = WasmSymbol() val scratchMemSizeInBytes = 65_536 + val stringPoolSize = WasmSymbol() + open class ReferencableElements { val unbound = mutableMapOf>() fun reference(ir: Ir): WasmSymbol { @@ -92,7 +96,6 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { val wasmToIr = mutableMapOf() } - @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() - val stringAddrs = mutableMapOf() - 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) }) + diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt index c805b91b505..5454099166f 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt @@ -25,6 +25,9 @@ class WasmModuleCodegenContextImpl( override val scratchMemAddr: WasmSymbol get() = wasmFragment.scratchMemAddr + override val stringPoolSize: WasmSymbol + 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 { - return wasmFragment.stringLiteralId.reference(string) + override fun referenceStringLiteralAddressAndId(string: String): Pair, WasmSymbol> { + val address = wasmFragment.stringLiteralAddress.reference(string) + val id = wasmFragment.stringLiteralPoolId.reference(string) + return address to id } override fun generateTypeInfo(irClass: IrClassSymbol, typeInfo: ConstantDataElement) { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/FieldInitializersLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/FieldInitializersLowering.kt index 0f687f970ec..c163b9ed5d6 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/FieldInitializersLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/FieldInitializersLowering.kt @@ -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 } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt index 443fb8fbbdd..1d49e3a7e0d 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt @@ -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 } } diff --git a/compiler/testData/codegen/box/constants/kt9532.kt b/compiler/testData/codegen/box/constants/kt9532.kt index 5e52a8666d6..bfd002ed60c 100644 --- a/compiler/testData/codegen/box/constants/kt9532.kt +++ b/compiler/testData/codegen/box/constants/kt9532.kt @@ -1,6 +1,3 @@ -// IGNORE_BACKEND: WASM -// WASM_MUTE_REASON: MINOR: CONST_EQUIVALENCE - object A { const val a: String = "$" const val b = "1234$a" diff --git a/compiler/testData/codegen/boxInline/special/identityCheck.kt b/compiler/testData/codegen/boxInline/special/identityCheck.kt index 8d92e42d36f..ae0a46df1d3 100644 --- a/compiler/testData/codegen/boxInline/special/identityCheck.kt +++ b/compiler/testData/codegen/boxInline/special/identityCheck.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: WASM // FILE: 1.kt package test diff --git a/libraries/stdlib/wasm/builtins/kotlin/Any.kt b/libraries/stdlib/wasm/builtins/kotlin/Any.kt index af3fdb6206b..07d4606dd1f 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Any.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Any.kt @@ -12,7 +12,7 @@ import kotlin.random.* /** * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass. */ -public open class Any { +public open class Any @WasmPrimitiveConstructor constructor() { // Pointer to runtime type info // Initialized by a compiler @Suppress("MUST_BE_INITIALIZED_OR_BE_ABSTRACT") diff --git a/libraries/stdlib/wasm/builtins/kotlin/Char.kt b/libraries/stdlib/wasm/builtins/kotlin/Char.kt index f6f62e69932..1ea8eb59184 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Char.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Char.kt @@ -102,8 +102,11 @@ public class Char private constructor(public val value: Char) : Comparable public inline fun toDouble(): Double = this.toInt().toDouble() - override fun toString(): String = - String(WasmCharArray(1).also { it.set(0, this) }) + override fun toString(): String { + val array = WasmCharArray(1) + array.set(0, this) + return array.createString() + } override fun hashCode(): Int = this.toInt().hashCode() diff --git a/libraries/stdlib/wasm/builtins/kotlin/String.kt b/libraries/stdlib/wasm/builtins/kotlin/String.kt index eca5088036b..540e63539db 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/String.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/String.kt @@ -12,30 +12,19 @@ import kotlin.math.min * The `String` class represents character strings. All string literals in Kotlin programs, such as `"abc"`, are * implemented as instances of this class. */ -public class String private constructor( +public class String internal @WasmPrimitiveConstructor constructor( private var leftIfInSum: String?, + public override val length: Int, private var _chars: WasmCharArray, ) : Comparable, CharSequence { public companion object {} - internal constructor(chars: WasmCharArray) : this(null, chars) { } - /** * Returns a string obtained by concatenating this string with the string representation of the given [other] object. */ public operator fun plus(other: Any?): String { - val right = if (other is String) other else other.toString() - return String(this, right.chars) - } - - public override val length: Int get() { - var currentLeftString = leftIfInSum - var currentLength = _chars.len() - while (currentLeftString != null) { - currentLength += currentLeftString._chars.len() - currentLeftString = currentLeftString.leftIfInSum - } - return currentLength + val right = other.toString() + return String(this, this.length + right.length, right.chars) } /** @@ -45,11 +34,8 @@ public class String private constructor( * where the behavior is unspecified. */ public override fun get(index: Int): Char { - if (index < 0) throw IndexOutOfBoundsException() - val folded = chars - val length = folded.len() - if (index >= length) throw IndexOutOfBoundsException() - return folded.get(index) + rangeCheck(index, this.length) + return chars.get(index) } internal fun foldChars() { @@ -79,59 +65,80 @@ public class String private constructor( public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { val actualStartIndex = startIndex.coerceAtLeast(0) - val thisChars = chars - val actualEndIndex = endIndex.coerceAtMost(thisChars.len()) - val newCharsLen = actualEndIndex - actualStartIndex - val newChars = WasmCharArray(newCharsLen) - copyWasmArray(thisChars, newChars, actualStartIndex, 0, newCharsLen) - return String(newChars) + val actualEndIndex = endIndex.coerceAtMost(this.length) + val newLength = actualEndIndex - actualStartIndex + if (newLength <= 0) return "" + val newChars = WasmCharArray(newLength) + copyWasmArray(chars, newChars, actualStartIndex, 0, newLength) + return newChars.createString() } public override fun compareTo(other: String): Int { + if (this === other) return 0 val thisChars = this.chars val otherChars = other.chars - val thisLength = thisChars.len() val otherLength = otherChars.len() - val len = min(thisLength, otherLength) + val minimumLength = if (thisLength < otherLength) thisLength else otherLength - for (i in 0 until len) { - val l = thisChars.get(i) - val r = otherChars.get(i) - if (l != r) - return l - r + repeat(minimumLength) { + val l = thisChars.get(it) + val r = otherChars.get(it) + if (l != r) return l - r } return thisLength - otherLength } - public override fun equals(other: Any?): Boolean = - other != null && - other is String && - (this.length == other.length) && - this.compareTo(other) == 0 + public override fun equals(other: Any?): Boolean { + if (other == null) return false + if (other === this) return true + val otherString = other as? String ?: return false + + val thisLength = this.length + val otherLength = otherString.length + if (thisLength != otherLength) return false + + val thisHash = this._hashCode + val otherHash = other._hashCode + if (thisHash != otherHash && thisHash != 0 && otherHash != 0) return false + + val thisChars = this.chars + val otherChars = other.chars + repeat(thisLength) { + if (thisChars.get(it) != otherChars.get(it)) return false + } + return true + } public override fun toString(): String = this public override fun hashCode(): Int { if (_hashCode != 0) return _hashCode - val thisChars = chars - val thisLength = thisChars.len() + val thisLength = this.length if (thisLength == 0) return 0 + val thisChars = chars var hash = 0 - var i = 0 - while (i < thisLength) { - hash = 31 * hash + thisChars.get(i).toInt() - i++ + repeat(thisLength) { + hash = (hash shl 5) - hash + thisChars.get(it).toInt() } - _hashCode = hash return _hashCode } } -internal fun stringLiteral(startAddr: Int, length: Int): String { - val array = WasmCharArray(length) - unsafeRawMemoryToWasmCharArray(startAddr, 0, length, array) - return String(array) -} +internal inline fun WasmCharArray.createString(): String = + String(null, this.len(), this) + +internal fun stringLiteral(poolId: Int, startAddress: Int, length: Int, hashCode: Int): String { + val cached = stringPool[poolId] + if (cached !== null) { + return cached + } + val chars = WasmCharArray(length) + unsafeRawMemoryToWasmCharArray(startAddress, 0, length, chars) + val newString = String(null, length, chars) + newString._hashCode = hashCode + stringPool[poolId] = newString + return newString +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt index 6b84c616b0c..a326cf5bf6e 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt @@ -9,7 +9,7 @@ import kotlin.wasm.internal.reftypes.anyref internal external interface ExternalInterfaceType -internal class JsExternalBox(val ref: ExternalInterfaceType) { +internal class JsExternalBox @WasmPrimitiveConstructor constructor(val ref: ExternalInterfaceType) { override fun toString(): String = externrefToString(ref) @@ -20,8 +20,13 @@ internal class JsExternalBox(val ref: ExternalInterfaceType) { false } - override fun hashCode(): Int = - externrefHashCode(ref) + override fun hashCode(): Int { + var hashCode = _hashCode + if (hashCode != 0) return hashCode + hashCode = externrefHashCode(ref) + _hashCode = hashCode + return hashCode + } } //language=js @@ -218,7 +223,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String { val stringLength = stringLength(x) val dstArray = WasmCharArray(stringLength) if (stringLength == 0) { - return String(dstArray) + return dstArray.createString() } val maxStringLength = unsafeGetScratchRawMemorySize() / CHAR_SIZE_BYTES @@ -233,7 +238,7 @@ internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String { jsExportStringToWasm(x, srcStartIndex, stringLength - srcStartIndex, memBuffer) unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, stringLength - srcStartIndex, dstArray) - return String(dstArray) + return dstArray.createString() } diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt index cd6218758af..5914c98f601 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Number2String.kt @@ -64,7 +64,7 @@ internal fun itoa32(inputValue: Int, radix: Int): String { if (sign == 1) buf.set(0, CharCodes.MINUS.code.toChar()) - return String(buf) + return buf.createString() } private fun utoaDecSimple(buffer: WasmCharArray, numInput: Int, offsetInput: Int) { @@ -144,7 +144,7 @@ internal fun itoa64(inputValue: Long, radix: Int): String { if (sign == 1) buf.set(0, CharCodes.MINUS.code.toChar()) - return String(buf) + return buf.createString() } // Count number of decimals for u64 values @@ -178,7 +178,7 @@ internal fun dtoa(value: Double): String { val size = dtoaCore(buf, value) val ret = WasmCharArray(size) buf.copyInto(ret, 0, 0, size) - return String(ret) + return ret.createString() } private fun dtoaCore(buffer: WasmCharArray, valueInp: Double): Int { diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt index e85ab9f9161..ec53faeda53 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Runtime.kt @@ -80,4 +80,12 @@ internal class Void private constructor() // This is the only way to introduce Void type. @WasmOp(WasmOp.DROP) internal fun consumeAnyIntoVoid(a: Any?): Void = - implementedAsIntrinsic \ No newline at end of file + implementedAsIntrinsic + +@ExcludedFromCodegen +internal fun stringGetPoolSize(): Int = + implementedAsIntrinsic + +// This initializer is a special case in FieldInitializersLowering +@EagerInitialization +internal val stringPool: Array = arrayOfNulls(stringGetPoolSize()) \ No newline at end of file diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt index 9a07d454a15..848cf82c0c2 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/TypeInfo.kt @@ -10,9 +10,11 @@ package kotlin.wasm.internal internal const val TYPE_INFO_ELEMENT_SIZE = 4 internal const val TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET = 0 -internal const val TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE +internal const val TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE +internal const val TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET + TYPE_INFO_ELEMENT_SIZE internal const val TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET = TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET + TYPE_INFO_ELEMENT_SIZE -internal const val TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE +internal const val TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET + TYPE_INFO_ELEMENT_SIZE +internal const val TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET + TYPE_INFO_ELEMENT_SIZE internal const val TYPE_INFO_SUPER_TYPE_OFFSET = TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET + TYPE_INFO_ELEMENT_SIZE internal const val TYPE_INFO_ITABLE_SIZE_OFFSET = TYPE_INFO_SUPER_TYPE_OFFSET + TYPE_INFO_ELEMENT_SIZE internal const val TYPE_INFO_ITABLE_OFFSET = TYPE_INFO_ITABLE_SIZE_OFFSET + TYPE_INFO_ELEMENT_SIZE @@ -21,11 +23,13 @@ internal class TypeInfoData(val typeId: Int, val isInterface: Boolean, val packa internal fun getTypeInfoTypeDataByPtr(typeInfoPtr: Int): TypeInfoData { val fqNameLength = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_LENGTH_OFFSET) - val fqNameLengthPtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET) + val fqNameId = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_ID_OFFSET) + val fqNamePtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_PACKAGE_NAME_PRT_OFFSET) val simpleNameLength = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_LENGTH_OFFSET) + val simpleNameId = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_ID_OFFSET) val simpleNamePtr = wasm_i32_load(typeInfoPtr + TYPE_INFO_TYPE_SIMPLE_NAME_PRT_OFFSET) - val packageName = stringLiteral(fqNameLengthPtr, fqNameLength) - val simpleName = stringLiteral(simpleNamePtr, simpleNameLength) + val packageName = stringLiteral(fqNameId, fqNamePtr, fqNameLength, 0) + val simpleName = stringLiteral(simpleNameId, simpleNamePtr, simpleNameLength, 0) return TypeInfoData(typeInfoPtr, isInterface = false, packageName, simpleName) } diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmAnnotations.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmAnnotations.kt index 7a81be80c2c..8ab1f8c5ed5 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmAnnotations.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmAnnotations.kt @@ -48,4 +48,13 @@ internal annotation class WasmAutoboxed @ExcludedFromCodegen internal val implementedAsIntrinsic: Nothing - get() = null!! \ No newline at end of file + get() = null!! + +/** + * Indicates that annotated constructor is primitive + * i.e. has direct layout from it's parameters to object fields (except any's fields) and has no code + * In this case no need to call it during an object creation. + */ +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +internal annotation class WasmPrimitiveConstructor \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt index a0e3d553941..e9afcaa2a69 100644 --- a/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt +++ b/libraries/stdlib/wasm/src/kotlin/text/StringBuilderWasm.kt @@ -15,7 +15,7 @@ internal fun insertString(array: CharArray, destinationIndex: Int, value: String internal fun unsafeStringFromCharArray(array: CharArray, start: Int, size: Int): String { val copy = WasmCharArray(size) copyWasmArray(array.storage, copy, start, 0, size) - return String(copy) + return copy.createString() } internal fun insertInt(array: CharArray, start: Int, value: Int): Int { diff --git a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt index 618ad01f156..8133991e113 100644 --- a/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt +++ b/libraries/stdlib/wasm/src/kotlin/text/StringsWasm.kt @@ -75,7 +75,7 @@ public actual fun String(chars: CharArray, offset: Int, length: Int): String { val copy = WasmCharArray(length) copyWasmArray(chars.storage, copy, offset, 0, length) - return String(copy) + return copy.createString() } /** @@ -88,7 +88,7 @@ public actual fun CharArray.concatToString(): String { val thisLength = thisStorage.len() val copy = WasmCharArray(thisLength) copyWasmArray(this.storage, copy, 0, 0, thisLength) - return String(copy) + return copy.createString() } /** @@ -109,7 +109,7 @@ public actual fun CharArray.concatToString(startIndex: Int = 0, endIndex: Int = val length = endIndex - startIndex val copy = WasmCharArray(length) copyWasmArray(this.storage, copy, startIndex, 0, length) - return String(copy) + return copy.createString() } /**