[Wasm] A name cache to distinguish same named declarations in DCE dump

This commit is contained in:
Sergei Kharitontcev-Beglov
2023-07-05 15:57:01 +02:00
committed by Space Team
parent bc0afc36a2
commit 34c6afec5e
8 changed files with 65 additions and 32 deletions
@@ -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<K2JSCompilerArguments>() {
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)
@@ -23,6 +23,7 @@ fun eliminateDeadDeclarations(
modules: Iterable<IrModuleFragment>,
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)
@@ -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<IrDeclaration, String> = hashMapOf()
private val indexCache: MutableMap<String, Int> = 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)"
}
}
@@ -235,7 +235,7 @@ abstract class UsefulDeclarationProcessor(
protected open fun handleAssociatedObjects(): Unit = Unit
fun collectDeclarations(rootDeclarations: Iterable<IrDeclaration>): Set<IrDeclaration> {
fun collectDeclarations(rootDeclarations: Iterable<IrDeclaration>, dceDumpNameCache: DceDumpNameCache): Set<IrDeclaration> {
rootDeclarations.forEach {
it.enqueue(it, "<ROOT>")
@@ -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<ReachabilityInfo>,
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<ReachabilityInfo>): String {
return transformToStringBy(reachabilityInfos, "\n") { sourceFqn, targetFqn, description, isTargetContagious ->
private fun transformToDotLikeString(reachabilityInfos: List<ReachabilityInfo>, 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<ReachabilityInfo>):
}
}
private fun transformToJsonString(reachabilityInfos: List<ReachabilityInfo>): String {
return "[\n" + transformToStringBy(reachabilityInfos, ",\n") { sourceFqn, targetFqn, description, isTargetContagious ->
private fun transformToJsonString(reachabilityInfos: List<ReachabilityInfo>, 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<ReachabilityInfo>): St
} + "\n]"
}
private fun transformToJsConstDeclaration(reachabilityInfos: List<ReachabilityInfo>): String {
return "export const kotlinReachabilityInfos = " + transformToJsonString(reachabilityInfos) + ";"
private fun transformToJsConstDeclaration(reachabilityInfos: List<ReachabilityInfo>, dceDumpNameCache: DceDumpNameCache): String {
return "export const kotlinReachabilityInfos = " + transformToJsonString(reachabilityInfos, dceDumpNameCache) + ";"
}
@@ -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<IrModuleFragment>) {
fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List<IrModuleFragment>, dceDumpNameCache: DceDumpNameCache) {
if (path == null) return
val declarations = linkedSetOf<IrDeclarationDumpInfo>()
@@ -53,7 +54,7 @@ fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List<IrModuleFragmen
type?.let {
declarations.add(
IrDeclarationDumpInfo(
fqName = declaration.fqNameForDceDump().removeQuotes(),
fqName = dceDumpNameCache.getOrPut(declaration).removeQuotes(),
type = it,
size = declaration.dumpKotlinLike().length
)
@@ -72,21 +73,13 @@ fun dumpDeclarationIrSizesIfNeed(path: String?, allModules: List<IrModuleFragmen
else -> 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)
}
@@ -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)
}
@@ -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<IrModuleFragment>, context: WasmBackendContext) {
fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, 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<IrModuleFragment>, 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 {
@@ -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,