[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:
committed by
Space Team
parent
84b5af3c89
commit
79d378f2bd
@@ -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())
|
||||
}
|
||||
|
||||
+2
-1
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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")
|
||||
|
||||
+17
-7
@@ -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
|
||||
|
||||
+5
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user