[JS IR] Perform optimizations on the generated JS code

The patch adopts and reuses the optimizations from the legacy backend.

The optimizations remove useless temporary variables,
statements and simplify generated JS code.

The optimizations can be disabled by `-Xoptimize-generated-js=false`.

Related to KT-51139
This commit is contained in:
Alexander Korepanov
2023-04-11 20:04:28 +02:00
committed by Space Team
parent 84b5af3c89
commit 79d378f2bd
87 changed files with 173 additions and 224 deletions
+1
View File
@@ -14,6 +14,7 @@ dependencies {
api(project(":js:js.ast"))
api(project(":js:js.frontend"))
api(project(":js:js.sourcemap"))
implementation(project(":js:js.translator"))
compileOnly(intellijCore())
}
@@ -95,7 +95,8 @@ internal class ICHasher {
val importantSettings = listOf(
JSConfigurationKeys.GENERATE_DTS,
JSConfigurationKeys.MODULE_KIND,
JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION
JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION,
JSConfigurationKeys.OPTIMIZE_GENERATED_JS
)
hashCalculator.updateForEach(importantSettings) { key ->
hashCalculator.update(key.toString())
@@ -8,7 +8,11 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
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.JsFunction
import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
fun optimizeProgramByIr(
modules: Iterable<IrModuleFragment>,
@@ -17,4 +21,14 @@ fun optimizeProgramByIr(
) {
eliminateDeadDeclarations(modules, context, removeUnusedAssociatedObjects)
jsOptimizationPhases.invokeToplevel(PhaseConfig(jsOptimizationPhases), context, modules)
}
}
fun optimizeFragmentByJsAst(fragment: JsIrProgramFragment) {
fragment.declarations.statements.forEach {
it.accept(object : RecursiveJsVisitor() {
override fun visitFunction(x: JsFunction) {
FunctionPostProcessor(x).apply()
}
})
}
}
@@ -7,15 +7,20 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.descriptors.ClassKind
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
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsExpression, JsGenerationContext> {
@@ -23,6 +28,12 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
private fun JsGenerationContext.isClassInlineLike(irClass: IrClass) =
staticContext.backendContext.inlineClassesUtils.isClassInlineLike(irClass)
override fun visitMemberAccess(expression: IrMemberAccessExpression<*>, data: JsGenerationContext): JsExpression {
return super.visitMemberAccess(expression, data).apply {
synthetic = expression.origin == JsStatementOrigins.SYNTHESIZED_STATEMENT
}
}
override fun visitComposite(expression: IrComposite, data: JsGenerationContext): JsExpression {
val size = expression.statements.size
if (size == 0) TODO("Empty IrComposite is not supported")
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.isTheLastReturnStatementIn
import org.jetbrains.kotlin.ir.backend.js.utils.isUnitInstanceFunction
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
@@ -24,6 +26,7 @@ import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsStatement, JsGenerationContext> {
@@ -161,7 +164,19 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
val jsInitializer = value?.accept(IrElementToJsExpressionTransformer(), context)
return JsVars(JsVars.JsVar(varName, jsInitializer).withSource(declaration, context, useNameOf = declaration))
val syntheticVariable = when (declaration.origin) {
is IrDeclarationOrigin.IR_TEMPORARY_VARIABLE -> true
is IrDeclarationOrigin.IR_TEMPORARY_VARIABLE_FOR_INLINED_PARAMETER -> true
is IrDeclarationOrigin.IR_TEMPORARY_VARIABLE_FOR_INLINED_EXTENSION_RECEIVER -> true
else -> false
}
val variable = JsVars.JsVar(varName, jsInitializer).apply {
withSource(declaration, context, useNameOf = declaration)
synthetic = syntheticVariable
}
return JsVars(variable).apply { synthetic = syntheticVariable }
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, context: JsGenerationContext): JsStatement {
@@ -172,7 +187,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
override fun visitCall(expression: IrCall, data: JsGenerationContext): JsStatement {
if (expression.symbol.isUnitInstanceFunction(data)) {
if (expression.symbol.isUnitInstanceFunction(data.staticContext.backendContext)) {
return JsEmpty
}
if (data.checkIfJsCode(expression.symbol) || data.checkIfHasAssociatedJsCode(expression.symbol)) {
@@ -181,11 +196,6 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
return translateCall(expression, data, IrElementToJsExpressionTransformer()).withSource(expression, data).makeStmt()
}
private fun IrFunctionSymbol.isUnitInstanceFunction(context: JsGenerationContext): Boolean {
return owner.origin === JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION &&
owner.returnType.classifierOrNull === context.staticContext.backendContext.irBuiltIns.unitClass
}
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, context: JsGenerationContext): JsStatement {
// TODO: implement
@@ -225,6 +225,7 @@ class IrModuleToJsTransformer(
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
private val optimizeGeneratedJs = backendContext.configuration.get(JSConfigurationKeys.OPTIMIZE_GENERATED_JS, true)
private fun generateProgramFragment(fileExports: IrFileExports, minimizedMemberNames: Boolean): JsIrProgramFragment {
val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames)
@@ -353,6 +354,10 @@ class IrModuleToJsTransformer(
}
}
if (optimizeGeneratedJs) {
optimizeFragmentByJsAst(result)
}
return result
}
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.js.config.SourceMapNamesPolicy
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
@@ -289,7 +291,14 @@ fun translateCall(
}
}
} else {
JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments)
JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments).pureIfPossible(function, context)
}
}
private fun JsInvocation.pureIfPossible(function: IrFunction, context: JsGenerationContext) = apply {
if (function.symbol.isUnitInstanceFunction(context.staticContext.backendContext)) {
sideEffects = SideEffectKind.PURE
qualifier.sideEffects = SideEffectKind.PURE
}
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.descriptors.isClass
import org.jetbrains.kotlin.descriptors.isInterface
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
@@ -20,8 +21,10 @@ import org.jetbrains.kotlin.ir.expressions.IrInlinedFunctionBlock
import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.parentAsClass
@@ -85,6 +88,11 @@ val IrClass.isInstantiableEnum: Boolean
val IrDeclaration.parentEnumClassOrNull: IrClass?
get() = parents.filterIsInstance<IrClass>().firstOrNull { it.isInstantiableEnum }
fun IrFunctionSymbol.isUnitInstanceFunction(context: JsIrBackendContext): Boolean {
return owner.origin === JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION &&
owner.returnType.classifierOrNull === context.irBuiltIns.unitClass
}
// TODO: the code is written to pass Repl tests, so we should understand. why in Repl tests we don't have backingField
fun JsIrBackendContext.getVoid(): IrExpression =
intrinsics.void.owner.backingField?.let {
@@ -81,7 +81,7 @@ class JsGenerationContext(
fun getNameForReturnableBlock(block: IrReturnableBlock): JsName? {
return nameCache.getOrPut(block) {
val name = localNames!!.localReturnableBlockNames.names[block] ?: return null
return JsName(name, true)
JsName(name, true)
}
}