diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt index 4ec62127f29..73e7e452e2b 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt @@ -43,7 +43,7 @@ open class PropertyAccessorInlineLowering( private fun canBeInlined(callee: IrSimpleFunction): Boolean { val property = callee.correspondingPropertySymbol?.owner ?: return false - // Some devirtualization required here + // Some de-virtualization required here if (!property.isSafeToInline(container)) return false val parent = property.parent diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 84ecf0f5ea0..6aaf3daee83 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -1000,12 +1000,27 @@ private val es6PrimaryConstructorUsageOptimizationLowering = makeBodyLoweringPha prerequisite = setOf(es6BoxParameterOptimization, es6PrimaryConstructorOptimizationLowering) ) +private val purifyObjectInstanceGetters = makeDeclarationTransformerPhase( + ::PurifyObjectInstanceGettersLowering, + name = "PurifyObjectInstanceGettersLowering", + description = "[Optimization] Make object instance getter functions pure whenever it's possible", +) + +private val inlineObjectsWithPureInitialization = makeBodyLoweringPhase( + ::InlineObjectsWithPureInitializationLowering, + name = "InlineObjectsWithPureInitializationLowering", + description = "[Optimization] Inline object instance fields getters whenever it's possible", + prerequisite = setOf(purifyObjectInstanceGetters) +) + val optimizationLoweringList = listOf( es6CollectConstructorsWhichNeedBoxParameterLowering, es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering, es6BoxParameterOptimization, es6PrimaryConstructorOptimizationLowering, es6PrimaryConstructorUsageOptimizationLowering, + purifyObjectInstanceGetters, + inlineObjectsWithPureInitialization ) val jsOptimizationPhases = SameTypeNamedCompilerPhase( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index 3df6460a828..f5d3acf9cc9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -35,6 +35,8 @@ class JsMapping : DefaultMapping() { val suspendArityStore = DefaultDelegateFactory.newDeclarationToDeclarationCollectionMapping>() + val objectsWithPureInitialization = DefaultDelegateFactory.newDeclarationToValueMapping() + val inlineFunctionsBeforeInlining = DefaultDelegateFactory.newDeclarationToDeclarationMapping() // Wasm mappings diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt index 864fcdf5bea..279b92f5d9c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -199,7 +199,7 @@ internal class JsUsefulDeclarationProcessor( !isExternal && !isExpect && !isBuiltInClass(this) override fun processConstructedClassDeclaration(declaration: IrDeclaration) { - if (declaration in result) return + if (declaration.isReachable()) return super.processConstructedClassDeclaration(declaration) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt index f3f0c237e7f..ae040ebdc7f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt @@ -108,7 +108,7 @@ abstract class UsefulDeclarationProcessor( contagiousReachableDeclarations.add(this as IrOverridableDeclaration<*>) } - if (this !in result) { + if (!isReachable()) { result.add(this) queue.addLast(this) @@ -181,7 +181,7 @@ abstract class UsefulDeclarationProcessor( } protected open fun processConstructedClassDeclaration(declaration: IrDeclaration) { - if (declaration in result) return + if (declaration.isReachable()) return fun IrOverridableDeclaration<*>.findOverriddenContagiousDeclaration(): IrOverridableDeclaration<*>? { for (overriddenSymbol in this.overriddenSymbols) { @@ -287,6 +287,8 @@ abstract class UsefulDeclarationProcessor( return result } + + protected fun IrDeclaration.isReachable(): Boolean = this in result } private data class ReachabilityInfo( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt index d2e3dcee684..3b3fcee7cde 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt @@ -143,14 +143,22 @@ object JsIrBuilder { fun buildSetVariable(symbol: IrVariableSymbol, value: IrExpression, type: IrType) = IrSetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, symbol, value, JsStatementOrigins.SYNTHESIZED_STATEMENT) - fun buildGetField(symbol: IrFieldSymbol, receiver: IrExpression?, superQualifierSymbol: IrClassSymbol? = null, type: IrType? = null) = + fun buildGetField( + symbol: IrFieldSymbol, + receiver: IrExpression? = null, + superQualifierSymbol: IrClassSymbol? = null, + type: IrType? = null, + startOffset: Int = UNDEFINED_OFFSET, + endOffset: Int = UNDEFINED_OFFSET, + origin: IrStatementOrigin? = JsStatementOrigins.SYNTHESIZED_STATEMENT + ) = IrGetFieldImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, + startOffset, + endOffset, symbol, type ?: symbol.owner.type, receiver, - JsStatementOrigins.SYNTHESIZED_STATEMENT, + origin, superQualifierSymbol ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt index fffbb81b88f..873e686e261 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt @@ -245,6 +245,9 @@ private tailrec fun IrExpression.isGetUnit(irBuiltIns: IrBuiltIns): Boolean = else -> false } + is IrConstructorCall -> + this.type == irBuiltIns.unitType + is IrGetObjectValue -> this.symbol == irBuiltIns.unitClass diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InlineObjectsWithPureInitializationLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InlineObjectsWithPureInitializationLowering.kt new file mode 100644 index 00000000000..7a58fed887a --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InlineObjectsWithPureInitializationLowering.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2023 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 org.jetbrains.kotlin.ir.backend.js.lower + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceGetter +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.expressions.IrBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +class InlineObjectsWithPureInitializationLowering(val context: JsCommonBackendContext) : BodyLoweringPass { + private val IrClass.instanceField by context.mapping.objectToInstanceField + private val IrClass.hasPureInitialization by context.mapping.objectsWithPureInitialization + + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitCall(expression: IrCall): IrExpression { + if (!expression.symbol.owner.isObjectInstanceGetter()) return super.visitCall(expression) + val objectToCreate = expression.symbol.owner.returnType.classOrNull?.owner ?: error("Expect return type of an object getter is an object type") + if (objectToCreate.hasPureInitialization != true) return super.visitCall(expression) + val instanceFieldForObject = objectToCreate.instanceField ?: error("An instance field for an object should exist") + return JsIrBuilder.buildGetField( + instanceFieldForObject.symbol, + startOffset = expression.startOffset, + endOffset = expression.endOffset, + origin = expression.origin + ) + } + }) + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InvokeStaticInitializersLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InvokeStaticInitializersLowering.kt index bc84952f440..385abdf94a1 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InvokeStaticInitializersLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InvokeStaticInitializersLowering.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.expressions.IrBody @@ -28,7 +29,15 @@ class InvokeStaticInitializersLowering(val context: JsIrBackendContext) : BodyLo val instance = context.mapping.objectToGetInstanceFunction[companionObject] ?: return - val getInstanceCall = IrCallImpl(irClass.startOffset, irClass.endOffset, context.irBuiltIns.unitType, instance.symbol, 0, 0) + val getInstanceCall = IrCallImpl( + irClass.startOffset, + irClass.endOffset, + context.irBuiltIns.unitType, + instance.symbol, + 0, + 0, + JsStatementOrigins.SYNTHESIZED_STATEMENT + ) (irBody as IrStatementContainer).statements.add(0, getInstanceCall) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PurifyObjectInstanceGettersLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PurifyObjectInstanceGettersLowering.kt new file mode 100644 index 00000000000..15f1bbb4937 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PurifyObjectInstanceGettersLowering.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2010-2023 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 org.jetbrains.kotlin.ir.backend.js.lower + +import org.jetbrains.kotlin.backend.common.DeclarationTransformer +import org.jetbrains.kotlin.backend.common.getOrPut +import org.jetbrains.kotlin.backend.common.ir.isPure +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceField +import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceGetter +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.util.* + +class PurifyObjectInstanceGettersLowering(val context: JsCommonBackendContext) : DeclarationTransformer { + private var IrClass.instanceField by context.mapping.objectToInstanceField + + override fun transformFlat(declaration: IrDeclaration): List? { + if ( + !declaration.isObjectInstanceGetter() && + !declaration.isObjectInstanceField() && + !declaration.isObjectConstructor() + ) return null + + return when (declaration) { + is IrSimpleFunction -> declaration.purifyObjectGetterIfPossible() + is IrField -> declaration.purifyObjectInstanceFieldIfPossible() + is IrConstructor -> declaration.removeInstanceFieldInitializationIfPossible() + else -> error("Unexpected IR type ${declaration::class.qualifiedName}") + } + } + + private fun IrConstructor.removeInstanceFieldInitializationIfPossible(): List? { + if (parentAsClass.isPureObject()) { + (body as? IrBlockBody)?.statements?.removeIf { + it is IrSetField && it.symbol.owner.isObjectInstanceField() + } + } + + return null + } + + private fun IrSimpleFunction.purifyObjectGetterIfPossible(): List? { + val objectToCreate = returnType.classOrNull?.owner ?: return null + + if (objectToCreate.isPureObject()) { + val body = (body as? IrBlockBody) ?: return null + val instanceField = objectToCreate.instanceField ?: error("Expect the object instance field to be created") + + body.statements.clear() + body.statements += JsIrBuilder.buildReturn( + symbol, + JsIrBuilder.buildGetField(instanceField.symbol), + objectToCreate.defaultType + ) + } + + return null + } + + private fun IrField.purifyObjectInstanceFieldIfPossible(): List? { + val objectToCreate = type.classOrNull?.owner ?: return null + + if (objectToCreate.isPureObject()) { + val objectConstructor = objectToCreate.primaryConstructor ?: error("Object should contain a primary constructor") + initializer = IrExpressionBodyImpl(JsIrBuilder.buildConstructorCall(objectConstructor.symbol)) + } + + return null + } + + private fun IrDeclaration.isObjectConstructor(): Boolean { + return this is IrConstructor && parentAsClass.isObject + } + + private fun IrClass.isPureObject(): Boolean { + return context.mapping.objectsWithPureInitialization.getOrPut(this) { + superClass == null && primaryConstructor?.body?.statements?.all { it.isPureStatementForObjectInitialization(this@isPureObject) } != false + } + } + + private fun IrStatement.isPureStatementForObjectInitialization(owner: IrClass): Boolean { + return ( + // Only objects which don't have a class parent + (this is IrDelegatingConstructorCall && symbol.owner.parent == context.irBuiltIns.anyClass.owner) || + (this is IrExpression && isPure(anyVariable = true, checkFields = false, context = context)) || + (this is IrContainerExpression && statements.all { it.isPureStatementForObjectInitialization(owner) }) || + (this is IrVariable && initializer?.isPureStatementForObjectInitialization(owner) != false) || + // Only fields of the objects are safe to not save an intermediate state of another class/object/global + (this is IrGetField && receiver?.isPureStatementForObjectInitialization(owner) == true) || + (this is IrSetField && receiver?.isPureStatementForObjectInitialization(owner) == true && value.isPureStatementForObjectInitialization(owner)) || + // Only current object could be initialized inside the object constructor, so we need to ignore it as an effect + (this is IrSetField && symbol.owner.isObjectInstanceField()) || + (this is IrSetValue && symbol.owner.isLocal && value.isPureStatementForObjectInitialization(owner)) + ) + + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/optimizations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/optimizations.kt index f8b94970ea4..f07c4510927 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/optimizations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/optimizations.kt @@ -10,8 +10,10 @@ import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.js.backend.ast.JsClass import org.jetbrains.kotlin.js.backend.ast.JsFunction import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor +import org.jetbrains.kotlin.js.inline.clean.ClassPostProcessor import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor fun optimizeProgramByIr( @@ -24,11 +26,20 @@ fun optimizeProgramByIr( } fun optimizeFragmentByJsAst(fragment: JsIrProgramFragment) { - fragment.declarations.statements.forEach { - it.accept(object : RecursiveJsVisitor() { - override fun visitFunction(x: JsFunction) { - FunctionPostProcessor(x).apply() - } - }) + val optimizer = object : RecursiveJsVisitor() { + override fun visitFunction(x: JsFunction) { + FunctionPostProcessor(x).apply() + } + + override fun visitClass(x: JsClass) { + super.visitClass(x) + ClassPostProcessor(x).apply() + } + } + + fragment.declarations.statements.forEach { it.accept(optimizer) } + fragment.classes.values.forEach { klass -> + klass.postDeclarationBlock.statements.forEach { it.accept(optimizer)} + klass.preDeclarationBlock.statements.forEach { it.accept(optimizer)} } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index c0f2ef18855..725116d790e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.utils.Namer import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName -import org.jetbrains.kotlin.ir.backend.js.utils.isUnitInstanceFunction import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.types.isString @@ -130,6 +129,12 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer, context: WasmBack dumpReachabilityInfoToFile ).collectDeclarations(rootDeclarations = buildRoots(modules, context)) - val remover = WasmUselessDeclarationsRemover(usefulDeclarations) + val remover = WasmUselessDeclarationsRemover(context, usefulDeclarations) modules.onAllFiles { acceptVoid(remover) } @@ -58,8 +59,8 @@ private fun buildRoots(modules: List, context: WasmBackendCont add(context.irBuiltIns.throwableClass.owner) add(context.mainCallsWrapperFunction) add(context.fieldInitFunction) - // TODO move Unit related optimization on IR level and make unit usages explicit - add(context.findUnitGetInstanceFunction()) + add(context.findUnitInstanceField()) + add(context.irBuiltIns.unitClass.owner.primaryConstructor!!) // Remove all functions used to call a kotlin closure from JS side, reachable ones will be added back later. removeAll(context.closureCallExports.values) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt index fecc0981076..8aebe4690cf 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt @@ -22,8 +22,6 @@ internal class WasmUsefulDeclarationProcessor( dumpReachabilityInfoToFile: String? ) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects = false, dumpReachabilityInfoToFile) { - private val unitGetInstance: IrSimpleFunction = context.findUnitGetInstanceFunction() - // The mapping from function for wrapping a kotlin closure/lambda with JS closure to function used to call a kotlin closure from JS side. private val kotlinClosureToJsClosureConvertFunToKotlinClosureCallFun = context.kotlinClosureToJsConverters.entries.associate { (k, v) -> v to context.closureCallExports[k] } @@ -49,6 +47,22 @@ internal class WasmUsefulDeclarationProcessor( super.visitVararg(expression, data) } + override fun visitSetField(expression: IrSetField, data: IrDeclaration) { + if (!expression.symbol.owner.isObjectInstanceField()) { + super.visitSetField(expression, data) + } + } + + override fun visitGetField(expression: IrGetField, data: IrDeclaration) { + val field = expression.symbol.owner + + if (field.isObjectInstanceField()) { + field.type.classOrFail.owner.primaryConstructor?.enqueue(field, "object lazy initialization") + } + + super.visitGetField(expression, data) + } + private fun tryToProcessIntrinsicCall(from: IrDeclaration, call: IrCall): Boolean = when (call.symbol) { context.wasmSymbols.unboxIntrinsic -> { val fromType = call.getTypeArgument(0) @@ -77,9 +91,6 @@ internal class WasmUsefulDeclarationProcessor( super.visitCall(expression, data) val function: IrFunction = expression.symbol.owner.realOverrideTarget - if (function.returnType == context.irBuiltIns.unitType) { - unitGetInstance.enqueue(data, "function Unit return type") - } if (tryToProcessIntrinsicCall(data, expression)) return if (function.hasWasmNoOpCastAnnotation()) return diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt index 3724aa91b95..955f9865711 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt @@ -5,17 +5,21 @@ package org.jetbrains.kotlin.backend.wasm.dce +import org.jetbrains.kotlin.backend.wasm.WasmBackendContext import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer -import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceField +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrSetField +import org.jetbrains.kotlin.ir.types.classOrFail +import org.jetbrains.kotlin.ir.util.primaryConstructor import org.jetbrains.kotlin.ir.util.transformFlat import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid class WasmUselessDeclarationsRemover( + private val context: WasmBackendContext, private val usefulDeclarations: Set ) : IrElementVisitorVoid { override fun visitElement(element: IrElement) { @@ -30,6 +34,14 @@ class WasmUselessDeclarationsRemover( process(declaration) } + override fun visitSimpleFunction(declaration: IrSimpleFunction) { + if (declaration == context.fieldInitFunction) { + declaration.removeUnusedObjectsInitializers() + } + + super.visitSimpleFunction(declaration) + } + // TODO bring back the primary constructor fix private fun process(container: IrDeclarationContainer) { container.declarations.transformFlat { member -> @@ -41,4 +53,10 @@ class WasmUselessDeclarationsRemover( } } } + + private fun IrSimpleFunction.removeUnusedObjectsInitializers() { + (body as? IrBlockBody)?.statements?.removeIf { + it is IrSetField && it.symbol.owner.isObjectInstanceField() && it.symbol.owner !in usefulDeclarations + } + } } \ No newline at end of file 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 ecde31b17ef..d8781e7c238 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 @@ -14,10 +14,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering -import org.jetbrains.kotlin.ir.backend.js.utils.erasedUpperBound -import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction -import org.jetbrains.kotlin.ir.backend.js.utils.isDispatchReceiver -import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget +import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol @@ -34,7 +31,6 @@ class BodyGenerator( val context: WasmModuleCodegenContext, val functionContext: WasmFunctionCodegenContext, private val hierarchyDisjointUnions: DisjointUnions, - private val isGetUnitFunction: Boolean, ) : IrElementVisitorVoid { val body: WasmExpressionBuilder = functionContext.bodyGen @@ -44,16 +40,13 @@ class BodyGenerator( private val irBuiltIns: IrBuiltIns = backendContext.irBuiltIns private val unitGetInstance by lazy { backendContext.findUnitGetInstanceFunction() } - fun WasmExpressionBuilder.buildGetUnit() { - buildInstr( - WasmOp.GET_UNIT, - SourceLocation.NoLocation("GET_UNIT"), - WasmImmediate.FuncIdx(context.referenceFunction(unitGetInstance.symbol)) - ) - } + private val unitInstanceField by lazy { backendContext.findUnitInstanceField() } - private val anyConstructor by lazy { - wasmSymbols.any.constructors.first { it.owner.valueParameters.isEmpty() } + fun WasmExpressionBuilder.buildGetUnit() { + buildGetGlobal( + context.referenceGlobalField(unitInstanceField.symbol), + SourceLocation.NoLocation("GET_UNIT") + ) } // Generates code for the given IR element. Leaves something on the stack unless expression was of the type Void. @@ -184,6 +177,7 @@ class BodyGenerator( val field: IrField = expression.symbol.owner val receiver: IrExpression? = expression.receiver val location = expression.getSourceLocation() + if (receiver != null) { generateExpression(receiver) if (backendContext.inlineClassesUtils.isClassInlineLike(field.parentAsClass)) { @@ -378,12 +372,6 @@ class BodyGenerator( return } - // Get unit is a special case because it is the only function which returns the real unit instance. - if (call.symbol == unitGetInstance.symbol) { - body.buildGetUnit() - return - } - // Some intrinsics are a special case because we want to remove them completely, including their arguments. if (!backendContext.configuration.getNotNull(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS)) { if (call.symbol == wasmSymbols.rangeCheck) { @@ -473,8 +461,9 @@ class BodyGenerator( } // Unit types don't cross function boundaries - if (function.returnType.isUnit()) + if (function.returnType.isUnit() && function !is IrConstructor) { body.buildGetUnit() + } } private fun generateRefNullCast(fromType: IrType, toType: IrType, location: SourceLocation) { @@ -701,14 +690,12 @@ class BodyGenerator( private fun visitFunctionReturn(expression: IrReturn) { val returnType = expression.returnTargetSymbol.owner.returnType(backendContext) - if (returnType == irBuiltIns.unitType && expression.returnTargetSymbol.owner != unitGetInstance) { - generateAsStatement(expression.value) - } else { - if (isGetUnitFunction) { - generateExpression(expression.value) - } else { - generateWithExpectedType(expression.value, returnType) - } + val isGetUnitFunction = expression.returnTargetSymbol.owner == unitGetInstance + + when { + isGetUnitFunction -> generateExpression(expression.value) + returnType == irBuiltIns.unitType -> generateAsStatement(expression.value) + else -> generateWithExpectedType(expression.value, returnType) } if (functionContext.irFunction is IrConstructor) { @@ -839,11 +826,7 @@ class BodyGenerator( if (!seenElse && resultType != null) { assert(expression.type != irBuiltIns.nothingType) if (expression.type.isUnit()) { - if (isGetUnitFunction) { - generateDefaultInitializerForType(resultType, body) - } else { - body.buildGetUnit() - } + body.buildGetUnit() } else { error("'When' without else branch and non Unit type: ${expression.type.dumpKotlinLike()}") } 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 ca28d412190..cb90ea12fbc 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 @@ -38,6 +38,7 @@ class DeclarationGenerator( private val irBuiltIns: IrBuiltIns = backendContext.irBuiltIns private val unitGetInstanceFunction: IrSimpleFunction by lazy { backendContext.findUnitGetInstanceFunction() } + private val unitPrimaryConstructor: IrConstructor? by lazy { backendContext.irBuiltIns.unitClass.owner.primaryConstructor } override fun visitElement(element: IrElement) { error("Unexpected element of type ${element::class}") @@ -98,12 +99,11 @@ class DeclarationGenerator( // Generate function type val watName = declaration.fqNameWhenAvailable.toString() val irParameters = declaration.getEffectiveValueParameters() - val resultType = - when { - // Unit_getInstance returns true Unit reference instead of "void" - declaration == unitGetInstanceFunction -> context.transformType(declaration.returnType) - else -> context.transformResultType(declaration.returnType) - } + val resultType = when (declaration) { + // Unit_getInstance returns true Unit reference instead of "void" + unitGetInstanceFunction, unitPrimaryConstructor -> context.transformType(declaration.returnType) + else -> context.transformResultType(declaration.returnType) + } val wasmFunctionType = WasmFunctionType( @@ -149,7 +149,6 @@ class DeclarationGenerator( context = context, functionContext = functionCodegenContext, hierarchyDisjointUnions = hierarchyDisjointUnions, - isGetUnitFunction = declaration == unitGetInstanceFunction ) if (declaration is IrConstructor) { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypes.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypes.kt index 14d02004862..16e07873514 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypes.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypes.kt @@ -103,6 +103,9 @@ val IrType.classOrNull: IrClassSymbol? else -> null } +val IrType.classOrFail: IrClassSymbol + get() = classOrNull ?: error("Expect type to be a class type") + val IrType.classFqName: FqName? get() = classOrNull?.owner?.fqNameWhenAvailable diff --git a/compiler/testData/codegen/box/size/add.kt b/compiler/testData/codegen/box/size/add.kt index 1b433b02883..3ffc31ed489 100644 --- a/compiler/testData/codegen/box/size/add.kt +++ b/compiler/testData/codegen/box/size/add.kt @@ -1,6 +1,6 @@ // TARGET_BACKEND: WASM -// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_463 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_338 // WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_579 // FILE: test.kt diff --git a/compiler/testData/codegen/box/size/helloWorld.kt b/compiler/testData/codegen/box/size/helloWorld.kt index 6ab6b2d02b7..02aae10f792 100644 --- a/compiler/testData/codegen/box/size/helloWorld.kt +++ b/compiler/testData/codegen/box/size/helloWorld.kt @@ -1,6 +1,6 @@ // TARGET_BACKEND: WASM -// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 48_188 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 47_948 // WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_605 fun box(): String { diff --git a/compiler/testData/codegen/box/size/helloWorldDOM.kt b/compiler/testData/codegen/box/size/helloWorldDOM.kt index 1bc092a0c97..249bb2f9b60 100644 --- a/compiler/testData/codegen/box/size/helloWorldDOM.kt +++ b/compiler/testData/codegen/box/size/helloWorldDOM.kt @@ -1,6 +1,6 @@ // TARGET_BACKEND: WASM -// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_828 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_699 // WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 6_134 // FILE: test.kt diff --git a/compiler/testData/codegen/box/size/objectsOptimization.kt b/compiler/testData/codegen/box/size/objectsOptimization.kt new file mode 100644 index 00000000000..aafe4ab8cc8 --- /dev/null +++ b/compiler/testData/codegen/box/size/objectsOptimization.kt @@ -0,0 +1,67 @@ +// TARGET_BACKEND: WASM + +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 18_008 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_451 + +object Simple + +object SimpleWithConstVal { + const val MAX = 4 +} + +object SimpleWithPureProperty { + val text = "Hello" +} + +object SimpleWithPropertyInitializedDurintInit { + val text: String + init { + text = "Hello" + } +} + +object SimpleWithFunctionsOnly { + fun foo() = "Foo" + fun bar() = "Bar" +} + +object SimpleWithDifferentMembers { + val foo = "Foo" + fun bar() = "Bar" +} + +interface Callable { + fun call(): String +} + +object SimpleWithInterface : Callable { + override fun call() = "OK" +} + +object UsedGetFieldInside { + val anotherText = SimpleWithPureProperty.text +} + +class ClassWithCompanion { + companion object +} + +class ClassWithCompanionWithConst { + companion object { + const val MAX = 5 + } +} + +fun box(): String { + if (Simple !is Any) return "Fail simple object" + if (SimpleWithConstVal.MAX != 4) return "Fail simple case with const val" + if (SimpleWithPureProperty.text != "Hello") return "Fail simple case with pure property" + if (SimpleWithPropertyInitializedDurintInit.text != "Hello") return "Fail simple case with pure property initialized inside init block" + if (SimpleWithFunctionsOnly.foo() != "Foo" || SimpleWithFunctionsOnly.bar() != "Bar") return "Fail simple case with functions only" + if (SimpleWithInterface.call() != "OK") return "Fail simple case with interface implementing" + if (UsedGetFieldInside.anotherText != "Hello") return "Fail object which used another object inside its initialization block" + if (ClassWithCompanion.Companion !is Any) return "Fail simple companion object" + if (ClassWithCompanionWithConst.MAX != 5) return "Fail simple companion object with const val" + SimpleWithDifferentMembers + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/size/ok.kt b/compiler/testData/codegen/box/size/ok.kt index ea17b788f36..37e9901d7c1 100644 --- a/compiler/testData/codegen/box/size/ok.kt +++ b/compiler/testData/codegen/box/size/ok.kt @@ -1,6 +1,6 @@ // TARGET_BACKEND: WASM -// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_505 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_380 // WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_451 fun box() = "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/box/size/removeUnusedOverride.kt b/compiler/testData/codegen/box/size/removeUnusedOverride.kt index 54e9513cc34..49e1089a182 100644 --- a/compiler/testData/codegen/box/size/removeUnusedOverride.kt +++ b/compiler/testData/codegen/box/size/removeUnusedOverride.kt @@ -1,7 +1,7 @@ // TARGET_BACKEND: JS_IR // TARGET_BACKEND: WASM -// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_005 +// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_883 interface I { fun foo() = "OK" diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ClassPostProcessor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ClassPostProcessor.kt new file mode 100644 index 00000000000..1aedc5c3051 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/ClassPostProcessor.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.inline.clean + +import org.jetbrains.kotlin.js.backend.ast.JsClass + +class ClassPostProcessor(val root: JsClass) { + val optimizations = listOf( + { EmptyConstructorRemoval(root).apply() } + ) + + fun apply() { + do { + var hasChanges = false + for (opt in optimizations) { + hasChanges = hasChanges or opt() + } + } while (hasChanges) + } +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyConstructorRemoval.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyConstructorRemoval.kt new file mode 100644 index 00000000000..820d1d6b10c --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyConstructorRemoval.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2023 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 org.jetbrains.kotlin.js.inline.clean + +import org.jetbrains.kotlin.js.backend.ast.JsClass + +class EmptyConstructorRemoval(private val klass: JsClass) { + fun apply(): Boolean { + if (klass.constructor?.body?.statements?.isEmpty() != true) return false + klass.constructor = null + return true + } +} \ No newline at end of file diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 13fddb80e79..695eac2d328 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -328,6 +328,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt"); } + @Test + @TestMetadata("objectLazyInitialized.kt") + public void testObjectLazyInitialized() throws Exception { + runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt"); + } + @Test @TestMetadata("setVar.kt") public void testSetVar() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 6803f25160b..7ccdaacc6c4 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -328,6 +328,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt"); } + @Test + @TestMetadata("objectLazyInitialized.kt") + public void testObjectLazyInitialized() throws Exception { + runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt"); + } + @Test @TestMetadata("setVar.kt") public void testSetVar() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 3612572ac6c..9e0ddbccd87 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -328,6 +328,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt"); } + @Test + @TestMetadata("objectLazyInitialized.kt") + public void testObjectLazyInitialized() throws Exception { + runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt"); + } + @Test @TestMetadata("setVar.kt") public void testSetVar() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index bb4d44dee69..41b3c88e91e 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -328,6 +328,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt"); } + @Test + @TestMetadata("objectLazyInitialized.kt") + public void testObjectLazyInitialized() throws Exception { + runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt"); + } + @Test @TestMetadata("setVar.kt") public void testSetVar() throws Exception { diff --git a/js/js.translator/testData/box/classObject/objectLazyInitialized.kt b/js/js.translator/testData/box/classObject/objectLazyInitialized.kt new file mode 100644 index 00000000000..c4139f2bc18 --- /dev/null +++ b/js/js.translator/testData/box/classObject/objectLazyInitialized.kt @@ -0,0 +1,55 @@ +// EXPECTED_REACHABLE_NODES: 1291 +// See KT-6201 +package foo + +import kotlin.reflect.KProperty + +var log = "" + +open class Mixin { + init { + log = "mixin" + } +} + +fun initCall(): Number { + log = "initCall" + return 2 +} + +object O1 { + init { + log = "O1 init" + } +} + +object O2 { + init { + initCall() + } +} + +object O3 : Mixin() + +object O4 { + val someValue = initCall() +} + +object O5 { + val someValue = O3.also { log = "O5 also" } +} + +fun box(): String { + if (log != "") return "Fail: something was initialized before any object was used" + O1 + if (log != "O1 init") return "Fail: O1 didn't initialized lazy" + O2 + if (log != "initCall") return "Fail: O2 didn't initialized lazy" + O3 + if (log != "mixin") return "Fail: O3 didn't initialized lazy" + O4 + if (log != "initCall") return "Fail: O4 didn't initialized lazy" + O5 + if (log != "O5 also") return "Fail: O5 didn't initialized lazy" + return "OK" +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt b/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt index 766593ade86..7c208c521a4 100644 --- a/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt +++ b/libraries/stdlib/wasm/src/generated/wasm/internal/_WasmOp.kt @@ -255,7 +255,6 @@ internal annotation class WasmOp(val name: String) { const val BR_ON_CAST_FAIL = "BR_ON_CAST_FAIL" const val EXTERN_INTERNALIZE = "EXTERN_INTERNALIZE" const val EXTERN_EXTERNALIZE = "EXTERN_EXTERNALIZE" - const val GET_UNIT = "GET_UNIT" const val PSEUDO_COMMENT_PREVIOUS_INSTR = "PSEUDO_COMMENT_PREVIOUS_INSTR" const val PSEUDO_COMMENT_GROUP_START = "PSEUDO_COMMENT_GROUP_START" const val PSEUDO_COMMENT_GROUP_END = "PSEUDO_COMMENT_GROUP_END" diff --git a/wasm/wasm.ir/build.gradle.kts b/wasm/wasm.ir/build.gradle.kts index 5aa0b988862..06b5c57887f 100644 --- a/wasm/wasm.ir/build.gradle.kts +++ b/wasm/wasm.ir/build.gradle.kts @@ -6,6 +6,8 @@ plugins { } dependencies { + compileOnly(project(":core:util.runtime")) + implementation(kotlinStdlib()) implementation(kotlinxCollectionsImmutable()) testImplementation(commonDependency("junit:junit")) diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt index d27413574fa..14202728066 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt @@ -386,9 +386,6 @@ enum class WasmOp( EXTERN_EXTERNALIZE("extern.externalize", 0xfb71), // anyref -> externref // ============================================================ - // Pseudo-instruction, just alias for a normal call. It's used to easily spot get_unit on the wasm level. - GET_UNIT("call", 0x10, FUNC_IDX), - PSEUDO_COMMENT_PREVIOUS_INSTR("", WASM_OP_PSEUDO_OPCODE), PSEUDO_COMMENT_GROUP_START("", WASM_OP_PSEUDO_OPCODE), PSEUDO_COMMENT_GROUP_END("", WASM_OP_PSEUDO_OPCODE), @@ -400,4 +397,4 @@ enum class WasmOp( const val WASM_OP_PSEUDO_OPCODE = 0xFFFF val opcodesToOp: Map = - enumValues().associateBy { it.opcode } + enumValues().associateBy { it.opcode } \ No newline at end of file diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt index b5d1f4ece4f..1ebfd785f98 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.wasm.ir +import org.jetbrains.kotlin.utils.addToStdlib.trimToSize import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation private fun WasmOp.isOutCfgNode() = when (this) { @@ -18,7 +19,7 @@ private fun WasmOp.isInCfgNode() = when (this) { } private fun WasmOp.pureStacklessInstruction() = when (this) { - WasmOp.GET_UNIT, WasmOp.REF_NULL, WasmOp.I32_CONST, WasmOp.I64_CONST, WasmOp.F32_CONST, WasmOp.F64_CONST, WasmOp.LOCAL_GET, WasmOp.GLOBAL_GET -> true + WasmOp.REF_NULL, WasmOp.I32_CONST, WasmOp.I64_CONST, WasmOp.F32_CONST, WasmOp.F64_CONST, WasmOp.LOCAL_GET, WasmOp.GLOBAL_GET -> true else -> false } @@ -30,13 +31,12 @@ class WasmIrExpressionBuilder( val expression: MutableList ) : WasmExpressionBuilder() { - private val lastInstr: WasmInstr? - get() = expression.lastOrNull() private var eatEverythingUntilLevel: Int? = null + private var lastInstructionIndex: Int = expression.indexOfLast { !it.operator.isPseudoInstruction } private fun addInstruction(op: WasmOp, location: SourceLocation, immediates: Array) { - val newInstruction = WasmInstrWithLocation(op, immediates.toList(), location) - expression.add(newInstruction) + expression += WasmInstrWithLocation(op, immediates.toList(), location) + if (!op.isPseudoInstruction) lastInstructionIndex = expression.lastIndex } private fun getCurrentEatLevel(op: WasmOp): Int? { @@ -64,16 +64,17 @@ class WasmIrExpressionBuilder( } } - val lastInstruction = lastInstr - if (lastInstruction == null) { + if (lastInstructionIndex == -1) { addInstruction(op, location, immediates) return } + + val lastInstruction = expression[lastInstructionIndex] val lastOperator = lastInstruction.operator // droppable instructions + drop/unreachable -> nothing if ((op == WasmOp.DROP || op == WasmOp.UNREACHABLE) && lastOperator.pureStacklessInstruction()) { - expression.removeLast() + trimInstructionsUntil(lastInstructionIndex) return } @@ -83,7 +84,7 @@ class WasmIrExpressionBuilder( if (localSetNumber != null) { val localGetNumber = (immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value if (localGetNumber == localSetNumber) { - expression.removeLast() + trimInstructionsUntil(lastInstructionIndex) addInstruction(WasmOp.LOCAL_TEE, location, immediates) return } @@ -93,11 +94,19 @@ class WasmIrExpressionBuilder( addInstruction(op, location, immediates) } + private fun trimInstructionsUntil(index: Int) { + expression.trimToSize(index) + lastInstructionIndex = index - 1 + } + override var numberOfNestedBlocks: Int = 0 set(value) { assert(value >= 0) { "end without matching block" } field = value } + + private val WasmOp.isPseudoInstruction: Boolean + get() = opcode == WASM_OP_PSEUDO_OPCODE } inline fun buildWasmExpression(body: WasmExpressionBuilder.() -> Unit): MutableList { diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/IrCodegenBoxWasmTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/IrCodegenBoxWasmTestGenerated.java index 22a2574883c..ac802c0bcf0 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/IrCodegenBoxWasmTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/IrCodegenBoxWasmTestGenerated.java @@ -31450,6 +31450,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest runTest("compiler/testData/codegen/box/size/helloWorldDOM.kt"); } + @TestMetadata("objectsOptimization.kt") + public void testObjectsOptimization() throws Exception { + runTest("compiler/testData/codegen/box/size/objectsOptimization.kt"); + } + @TestMetadata("ok.kt") public void testOk() throws Exception { runTest("compiler/testData/codegen/box/size/ok.kt");