diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 11a2820f08d..adb30ba568f 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.ir.backend.js.* +import org.jetbrains.kotlin.ir.backend.js.dce.DceDumpNameCache import org.jetbrains.kotlin.ir.backend.js.dce.dumpDeclarationIrSizesIfNeed import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.* @@ -347,11 +348,12 @@ class K2JsIrCompiler : CLICompiler() { exportedDeclarations = setOf(FqName("main")), propertyLazyInitialization = arguments.irPropertyLazyInitialization, ) + val dceDumpNameCache = DceDumpNameCache() if (arguments.irDce) { - eliminateDeadDeclarations(allModules, backendContext) + eliminateDeadDeclarations(allModules, backendContext, dceDumpNameCache) } - dumpDeclarationIrSizesIfNeed(arguments.irDceDumpDeclarationIrSizesToFile, allModules) + dumpDeclarationIrSizesIfNeed(arguments.irDceDumpDeclarationIrSizesToFile, allModules, dceDumpNameCache) val generateSourceMaps = configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt index db15f4d54c6..809920a94ad 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt @@ -23,6 +23,7 @@ fun eliminateDeadDeclarations( modules: Iterable, context: JsIrBackendContext, removeUnusedAssociatedObjects: Boolean = true, + dceDumpNameCache: DceDumpNameCache, ) { val allRoots = buildRoots(modules, context) @@ -31,7 +32,7 @@ fun eliminateDeadDeclarations( java.lang.Boolean.getBoolean("kotlin.js.ir.dce.print.reachability.info") val usefulDeclarationProcessor = JsUsefulDeclarationProcessor(context, printReachabilityInfo, removeUnusedAssociatedObjects) - val usefulDeclarations = usefulDeclarationProcessor.collectDeclarations(allRoots) + val usefulDeclarations = usefulDeclarationProcessor.collectDeclarations(allRoots, dceDumpNameCache) val uselessDeclarationsProcessor = UselessDeclarationsRemover(removeUnusedAssociatedObjects, usefulDeclarations, context, context.dceRuntimeDiagnostic) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/DceDumpNameCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/DceDumpNameCache.kt new file mode 100644 index 00000000000..5b47334251a --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/DceDumpNameCache.kt @@ -0,0 +1,26 @@ +/* + * 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.dce + +import org.jetbrains.kotlin.ir.declarations.IrDeclaration + +class DceDumpNameCache { + /** + * This cache provides better dumping names for reachability and ir sizes infos. + * The problem is that dumps contain several same named objects that should be consistently renamed. + * The name for the first declaration with some name isn't changed. For other names suffix (i) is added, where i >= 1 + */ + private val nameCache: MutableMap = hashMapOf() + private val indexCache: MutableMap = hashMapOf() + + public fun getOrPut(declaration: IrDeclaration): String = nameCache.getOrPut(declaration) { + val fqName = declaration.fqNameForDceDump() + val index = indexCache.getOrDefault(fqName, 0) + indexCache[fqName] = index + 1 + if (index == 0) fqName + else "$fqName ($index)" + } +} \ No newline at end of file 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 b74eefa4b3b..9b78c877552 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 @@ -235,7 +235,7 @@ abstract class UsefulDeclarationProcessor( protected open fun handleAssociatedObjects(): Unit = Unit - fun collectDeclarations(rootDeclarations: Iterable): Set { + fun collectDeclarations(rootDeclarations: Iterable, dceDumpNameCache: DceDumpNameCache): Set { rootDeclarations.forEach { it.enqueue(it, "") @@ -274,7 +274,7 @@ abstract class UsefulDeclarationProcessor( if (reachabilityInfos != null) { if (printReachabilityInfo) { - println(transformToDotLikeString(reachabilityInfos)) + println(transformToDotLikeString(reachabilityInfos, dceDumpNameCache)) } if (dumpReachabilityInfoToFile != null) { @@ -285,7 +285,7 @@ abstract class UsefulDeclarationProcessor( else -> ::transformToDotLikeString } - out.writeText(stringify(reachabilityInfos)) + out.writeText(stringify(reachabilityInfos, dceDumpNameCache)) } } @@ -305,18 +305,24 @@ private data class ReachabilityInfo( private fun transformToStringBy( reachabilityInfos: List, separator: String, - transformer: (sourceFqn: String, targetFqn: String, description: String, isTargetContagious: Boolean) -> String + dceDumpNameCache: DceDumpNameCache, + transformer: (sourceFqn: String, targetFqn: String, description: String, isTargetContagious: Boolean) -> String, ): String { return reachabilityInfos .map { - transformer(it.source.fqNameForDceDump(), it.target.fqNameForDceDump(), it.description, it.isTargetContagious) + transformer( + dceDumpNameCache.getOrPut(it.source), + dceDumpNameCache.getOrPut(it.target), + it.description, + it.isTargetContagious + ) } .distinct() .joinToString(separator) } -private fun transformToDotLikeString(reachabilityInfos: List): String { - return transformToStringBy(reachabilityInfos, "\n") { sourceFqn, targetFqn, description, isTargetContagious -> +private fun transformToDotLikeString(reachabilityInfos: List, dceDumpNameCache: DceDumpNameCache): String { + return transformToStringBy(reachabilityInfos, "\n", dceDumpNameCache) { sourceFqn, targetFqn, description, isTargetContagious -> val comment = description + (if (isTargetContagious) "[CONTAGIOUS!]" else "") val info = "\"$sourceFqn\" -> \"$targetFqn\"" + (if (comment.isBlank()) "" else " // $comment") @@ -324,8 +330,8 @@ private fun transformToDotLikeString(reachabilityInfos: List): } } -private fun transformToJsonString(reachabilityInfos: List): String { - return "[\n" + transformToStringBy(reachabilityInfos, ",\n") { sourceFqn, targetFqn, description, isTargetContagious -> +private fun transformToJsonString(reachabilityInfos: List, dceDumpNameCache: DceDumpNameCache): String { + return "[\n" + transformToStringBy(reachabilityInfos, ",\n", dceDumpNameCache) { sourceFqn, targetFqn, description, isTargetContagious -> """ | { | "source" : "${sourceFqn.removeQuotes()}", @@ -336,6 +342,6 @@ private fun transformToJsonString(reachabilityInfos: List): St } + "\n]" } -private fun transformToJsConstDeclaration(reachabilityInfos: List): String { - return "export const kotlinReachabilityInfos = " + transformToJsonString(reachabilityInfos) + ";" +private fun transformToJsConstDeclaration(reachabilityInfos: List, dceDumpNameCache: DceDumpNameCache): String { + return "export const kotlinReachabilityInfos = " + transformToJsonString(reachabilityInfos, dceDumpNameCache) + ";" } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/utils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/utils.kt index 4203c49a0ba..9c843252a49 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/utils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/utils.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.dce import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.util.dumpKotlinLike @@ -31,7 +32,7 @@ internal fun IrDeclaration.fqNameForDceDump(): String { private data class IrDeclarationDumpInfo(val fqName: String, val type: String, val size: Int) -fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List) { +fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List, dceDumpNameCache: DceDumpNameCache) { if (path == null) return val declarations = linkedSetOf() @@ -53,7 +54,7 @@ fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List listOf("", "", "\n", "") } - val value = declarations - // TODO: introduce a way to assign unique names to each declaration and use it for both reachability and size infos - .groupBy { it.fqName } - .flatMap { (_, v) -> - v.mapIndexed { index: Int, declaration: IrDeclarationDumpInfo -> - if (index == 0) declaration - else declaration.copy(fqName = "${declaration.fqName} ($index)") - } - }.joinToString(separator, prefix, postfix) { declaration -> - """$indent"${declaration.fqName}": { + val value = declarations.joinToString(separator, prefix, postfix) { declaration -> + """$indent"${declaration.fqName}": { |$indent$indent"size": ${declaration.size}, |$indent$indent"type": "${declaration.type}" |$indent} """.trimMargin() - } + } out.writeText(value) } 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 f07c4510927..23b6e409542 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 @@ -7,6 +7,7 @@ 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.DceDumpNameCache 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 @@ -21,7 +22,8 @@ fun optimizeProgramByIr( context: JsIrBackendContext, removeUnusedAssociatedObjects: Boolean ) { - eliminateDeadDeclarations(modules, context, removeUnusedAssociatedObjects) + val dceDumpNameCache = DceDumpNameCache() // in JS mode only DCE Graph could be dumped + eliminateDeadDeclarations(modules, context, removeUnusedAssociatedObjects, dceDumpNameCache) jsOptimizationPhases.invokeToplevel(PhaseConfig(jsOptimizationPhases), context, modules) } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt index 37bdab6b957..272c6e76847 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt @@ -7,6 +7,7 @@ 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.backend.js.dce.DceDumpNameCache import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrBody @@ -16,7 +17,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.js.config.JSConfigurationKeys -fun eliminateDeadDeclarations(modules: List, context: WasmBackendContext) { +fun eliminateDeadDeclarations(modules: List, context: WasmBackendContext, dceDumpNameCache: DceDumpNameCache) { val printReachabilityInfo = context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) || java.lang.Boolean.getBoolean("kotlin.wasm.dce.print.reachability.info") @@ -29,7 +30,7 @@ fun eliminateDeadDeclarations(modules: List, context: WasmBack context = context, printReachabilityInfo = printReachabilityInfo, dumpReachabilityInfoToFile - ).collectDeclarations(rootDeclarations = buildRoots(modules, context)) + ).collectDeclarations(rootDeclarations = buildRoots(modules, context), dceDumpNameCache) val remover = WasmUselessDeclarationsRemover(context, usefulDeclarations) modules.onAllFiles { diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt index dd2cac06a10..41a81e99c43 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.backend.wasm.wasmPhases import org.jetbrains.kotlin.ir.backend.js.MainModule import org.jetbrains.kotlin.ir.backend.js.ModulesStructure +import org.jetbrains.kotlin.ir.backend.js.dce.DceDumpNameCache import org.jetbrains.kotlin.ir.backend.js.dce.dumpDeclarationIrSizesIfNeed import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageConfig @@ -97,9 +98,10 @@ class WasmBackendFacade( generateWat = generateWat, ) - eliminateDeadDeclarations(allModules, backendContext) + val dceDumpNameCache = DceDumpNameCache() + eliminateDeadDeclarations(allModules, backendContext, dceDumpNameCache) - dumpDeclarationIrSizesIfNeed(System.getProperty("kotlin.wasm.dump.declaration.ir.size.to.file"), allModules) + dumpDeclarationIrSizesIfNeed(System.getProperty("kotlin.wasm.dump.declaration.ir.size.to.file"), allModules, dceDumpNameCache) val compilerResultWithDCE = compileWasm( allModules = allModules,