[K/JS, K/Wasm] Optimize simple objects declaration and usage ^Fixed KT-58797

This commit is contained in:
Artem Kobzar
2023-06-02 14:23:40 +00:00
committed by Space Team
parent 7d6275e228
commit bfd57fd2df
39 changed files with 551 additions and 90 deletions
@@ -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
@@ -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<Lowering>(
es6CollectConstructorsWhichNeedBoxParameterLowering,
es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering,
es6BoxParameterOptimization,
es6PrimaryConstructorOptimizationLowering,
es6PrimaryConstructorUsageOptimizationLowering,
purifyObjectInstanceGetters,
inlineObjectsWithPureInitialization
)
val jsOptimizationPhases = SameTypeNamedCompilerPhase(
@@ -35,6 +35,8 @@ class JsMapping : DefaultMapping() {
val suspendArityStore = DefaultDelegateFactory.newDeclarationToDeclarationCollectionMapping<IrClass, Collection<IrSimpleFunction>>()
val objectsWithPureInitialization = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, Boolean>()
val inlineFunctionsBeforeInlining = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrFunction, IrFunction>()
// Wasm mappings
@@ -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)
@@ -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(
@@ -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
)
@@ -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
@@ -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
)
}
})
}
}
@@ -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)
}
@@ -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<IrDeclaration>? {
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<IrDeclaration>? {
if (parentAsClass.isPureObject()) {
(body as? IrBlockBody)?.statements?.removeIf {
it is IrSetField && it.symbol.owner.isObjectInstanceField()
}
}
return null
}
private fun IrSimpleFunction.purifyObjectGetterIfPossible(): List<IrDeclaration>? {
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<IrDeclaration>? {
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))
)
}
}
@@ -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)}
}
}
@@ -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<JsEx
}
val fieldName = context.getNameForField(field)
return jsElementAccess(fieldName, expression.receiver?.accept(this, context)).withSource(expression, context)
.apply {
if (field.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE && expression.origin == JsStatementOrigins.SYNTHESIZED_STATEMENT) {
synthetic = true
sideEffects = SideEffectKind.PURE
}
}
}
override fun visitGetValue(expression: IrGetValue, context: JsGenerationContext): JsExpression {
@@ -112,3 +112,18 @@ fun irEmpty(context: JsIrBackendContext): IrExpression {
return JsIrBuilder.buildComposite(context.dynamicType, emptyList())
}
fun IrDeclaration.isObjectInstanceGetter(): Boolean {
return this is IrSimpleFunction && isObjectInstanceGetter()
}
fun IrSimpleFunction.isObjectInstanceGetter(): Boolean {
return origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION
}
fun IrDeclaration.isObjectInstanceField(): Boolean {
return this is IrField && isObjectInstanceField()
}
fun IrField.isObjectInstanceField(): Boolean {
return origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE
}
@@ -107,6 +107,9 @@ fun IrBody.prependFunctionCall(
fun JsCommonBackendContext.findUnitGetInstanceFunction(): IrSimpleFunction =
mapping.objectToGetInstanceFunction[irBuiltIns.unitClass.owner]!!
fun JsCommonBackendContext.findUnitInstanceField(): IrField =
mapping.objectToInstanceField[irBuiltIns.unitClass.owner]!!
fun IrDeclaration.isImportedFromModuleOnly(): Boolean {
return isTopLevel && isEffectivelyExternal() && (getJsModule() != null && !isJsNonModule() || (parent as? IrAnnotationContainer)?.getJsModule() != null)
}
@@ -610,6 +610,20 @@ private val unitToVoidLowering = makeWasmModulePhase(
description = "Replace some Unit's with Void's"
)
private val purifyObjectInstanceGettersLoweringPhase = makeWasmModulePhase(
::PurifyObjectInstanceGettersLowering,
name = "PurifyObjectInstanceGettersLowering",
description = "[Optimization] Make object instance getter functions pure whenever it's possible",
prerequisite = setOf(objectDeclarationLoweringPhase, objectUsageLoweringPhase)
)
private val inlineObjectsWithPureInitializationLoweringPhase = makeWasmModulePhase(
::InlineObjectsWithPureInitializationLowering,
name = "InlineObjectsWithPureInitializationLowering",
description = "[Optimization] Inline object instance fields getters whenever it's possible",
prerequisite = setOf(purifyObjectInstanceGettersLoweringPhase)
)
val wasmPhases = SameTypeNamedCompilerPhase(
name = "IrModuleLowering",
description = "IR module lowering",
@@ -707,7 +721,6 @@ val wasmPhases = SameTypeNamedCompilerPhase(
associatedObjectsLowering then
objectDeclarationLoweringPhase then
fieldInitializersLoweringPhase then
genericReturnTypeLowering then
unitToVoidLowering then
@@ -715,8 +728,12 @@ val wasmPhases = SameTypeNamedCompilerPhase(
builtInsLoweringPhase0 then
autoboxingTransformerPhase then
explicitlyCastExternalTypesPhase then
objectUsageLoweringPhase then
purifyObjectInstanceGettersLoweringPhase then
fieldInitializersLoweringPhase then
explicitlyCastExternalTypesPhase then
typeOperatorLoweringPhase then
// Clean up built-ins after type operator lowering
@@ -724,5 +741,6 @@ val wasmPhases = SameTypeNamedCompilerPhase(
virtualDispatchReceiverExtractionPhase then
staticMembersLoweringPhase then
inlineObjectsWithPureInitializationLoweringPhase then
validateIrAfterLowering
)
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -30,7 +31,7 @@ fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, 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<IrModuleFragment>, 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)
@@ -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
@@ -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<IrDeclaration>
) : 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
}
}
}
@@ -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<IrClassSymbol>,
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()}")
}
@@ -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) {
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
@@ -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"
}
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
@@ -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)
}
}
@@ -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
}
}
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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"
}
@@ -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"
+2
View File
@@ -6,6 +6,8 @@ plugins {
}
dependencies {
compileOnly(project(":core:util.runtime"))
implementation(kotlinStdlib())
implementation(kotlinxCollectionsImmutable())
testImplementation(commonDependency("junit:junit"))
@@ -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("<comment-single>", WASM_OP_PSEUDO_OPCODE),
PSEUDO_COMMENT_GROUP_START("<comment-group-start>", WASM_OP_PSEUDO_OPCODE),
PSEUDO_COMMENT_GROUP_END("<comment-group-end>", WASM_OP_PSEUDO_OPCODE),
@@ -400,4 +397,4 @@ enum class WasmOp(
const val WASM_OP_PSEUDO_OPCODE = 0xFFFF
val opcodesToOp: Map<Int, WasmOp> =
enumValues<WasmOp>().associateBy { it.opcode }
enumValues<WasmOp>().associateBy { it.opcode }
@@ -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<WasmInstr>
) : 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<out WasmImmediate>) {
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<WasmInstr> {
@@ -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");