[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
@@ -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)
}