From 0506d422a399ba9dcc5fb000ad26fd3f74ba1ada Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Mon, 25 Oct 2021 13:20:39 +0300 Subject: [PATCH] [JS IR] support per-module in the new Ir2JS --- .../kotlin/ir/backend/js/compilerWithIC.kt | 15 +- .../js/lower/PropertyLazyInitLowering.kt | 7 +- .../irToJs/IrModuleToJsTransformerTmp.kt | 98 +++++- .../irToJs/JsIrProgramFragment.kt | 319 ++++++------------ .../backend/js/transformers/irToJs/Merger.kt | 256 ++++++++++++++ .../js/test/converters/JsIrBackendFacade.kt | 3 +- .../box/crossModuleRefIR/topLevelFunction.kt | 10 + .../box/jsName/jsNamePropertyAccessors.kt | 1 - 8 files changed, 463 insertions(+), 246 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt index 19e6932b23b..5dbb93ca947 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt @@ -14,9 +14,7 @@ import org.jetbrains.kotlin.ir.backend.js.ic.PersistentCacheConsumer import org.jetbrains.kotlin.ir.backend.js.lower.generateJsTests import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.generateWrappedModuleBody +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.* import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer import org.jetbrains.kotlin.ir.declarations.IrFactory import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -115,6 +113,13 @@ fun generateJsFromAst( caches: Map ): CompilerResult { val deserializer = JsIrAstDeserializer() - val fragments = caches.values.map { it.asts.values.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it))} } } - return CompilerResult(generateWrappedModuleBody("main", ModuleKind.PLAIN, fragments), null) + val fragments = JsIrProgram(caches.values.map { JsIrModule(it.name, it.name, it.asts.values.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it))} }) }) + return CompilerResult( + generateSingleWrappedModuleBody( + "main", + ModuleKind.PLAIN, + fragments.modules.flatMap { it.fragments }, + generateCallToMain = true, + ), null + ) } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt index 1fbc224946f..44aa2acfc76 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt @@ -279,10 +279,9 @@ private fun IrDeclaration.withPersistentSafe(transform: IrDeclaration.() -> } else null private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) = - correspondingProperty - ?.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) != true && - withPersistentSafe { origin in compatibleOrigins } == true - + correspondingProperty?.let { + it.isForLazyInit() && !it.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) + } ?: true && withPersistentSafe { origin in compatibleOrigins } == true private fun IrDeclaration.assertCompatibleDeclaration() { assert(this !is PersistentIrElementBase<*> || this.createdOn <= this.factory.stageController.currentStage) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt index 0eba123859d..15f72524a5c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt @@ -57,12 +57,24 @@ class IrModuleToJsTransformerTmp( module.files.forEach { StaticMembersLowering(backendContext).lower(it) } } - val jsCode = if (fullJs) generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) else null + val jsCode = if (fullJs) generateWrappedModuleBody( + multiModule, + mainModuleName, + moduleKind, + generateProgramFragments(modules, exportData), + relativeRequirePath + ) else null val dceJsCode = if (dceJs) { eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects) - generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) + generateWrappedModuleBody( + multiModule, + mainModuleName, + moduleKind, + generateProgramFragments(modules, exportData), + relativeRequirePath + ) } else null return CompilerResult(jsCode, dceJsCode, dts) @@ -98,20 +110,23 @@ class IrModuleToJsTransformerTmp( return additionalExports + exports } + private fun IrModuleFragment.externalModuleName(): String { + return moduleToName[this] ?: sanitizeName(safeName) + } + private fun generateProgramFragments( modules: Iterable, exportData: Map>>, - ): List> { + ): JsIrProgram { - val fragments = mutableMapOf() - modules.forEach { m -> - m.files.forEach { f -> - val exports = exportData[m]!![f]!! // TODO - fragments[f] = generateProgramFragment(f, exports) + return JsIrProgram( + modules.map { m -> + JsIrModule(m.safeName, m.externalModuleName(), m.files.map { f -> + val exports = exportData[m]!![f]!! + generateProgramFragment(f, exports) + }) } - } - - return modules.map { it.files.map { fragments[it]!! } } + ) } private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH) @@ -213,6 +228,10 @@ class IrModuleToJsTransformerTmp( result.imports[tag] = importExpression } + file.declarations.forEach { + result.definitions += computeTag(it) + } + return result } @@ -236,17 +255,66 @@ class IrModuleToJsTransformerTmp( } } -fun generateWrappedModuleBody( +private fun generateWrappedModuleBody( + multiModule: Boolean, + mainModuleName: String, + moduleKind: ModuleKind, + program: JsIrProgram, + relativeRequirePath: Boolean, +): CompilationOutputs { + if (multiModule) { + + val moduleToRef = program.crossModuleDependencies(relativeRequirePath) + + val main = program.modules.last() + val others = program.modules.dropLast(1) + + val mainModule = generateSingleWrappedModuleBody( + mainModuleName, + moduleKind, + main.fragments, + moduleToRef[main]!!, + generateCallToMain = true, + ) + + val dependencies = others.map { module -> + val moduleName = module.moduleName + + moduleName to generateSingleWrappedModuleBody( + moduleName, + moduleKind, + module.fragments, + moduleToRef[module]!!, + generateCallToMain = false, + ) + } + + return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies) + } else { + return generateSingleWrappedModuleBody( + mainModuleName, + moduleKind, + program.modules.flatMap { it.fragments }, + generateCallToMain = true, + ) + } +} + +fun generateSingleWrappedModuleBody( moduleName: String, moduleKind: ModuleKind, - fragments: List> + fragments: List, + crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty, + generateCallToMain: Boolean, ): CompilationOutputs { val program = Merger( moduleName, moduleKind, fragments, - false, - true + crossModuleReferences, + generateScriptModule = false, + generateRegionComments = true, + generateCallToMain, ).merge() program.resolveTemporaryNames() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index ad274181b2c..f8a4db2a478 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -5,12 +5,8 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs -import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope -import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName import org.jetbrains.kotlin.js.backend.ast.* -import org.jetbrains.kotlin.serialization.js.ModuleKind -import org.jetbrains.kotlin.utils.DFS class JsIrProgramFragment(val packageFqn: String) { val nameBindings = mutableMapOf() @@ -24,223 +20,106 @@ class JsIrProgramFragment(val packageFqn: String) { var mainFunction: JsStatement? = null var testFunInvocation: JsStatement? = null var suiteFn: JsName? = null + val definitions = mutableSetOf() } -class Merger( - private val moduleName: String, - private val moduleKind: ModuleKind, - private val fragments: List>, - private val generateScriptModule: Boolean, - private val generateRegionComments: Boolean, +class JsIrModule(val moduleName: String, val externalModuleName: String, val fragments: List) + +class JsIrProgram(val modules: List) { + + fun crossModuleDependencies(relativeRequirePath: Boolean): Map { + val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) } + val definitionModule = mutableMapOf() + + for (module in modules) { + val moduleBuilder = moduleToBuilder[module]!! + for (fragment in module.fragments) { + for (definition in fragment.definitions) { + require(definition !in definitionModule) { "Duplicate definition: $definition" } + definitionModule[definition] = moduleBuilder + } + } + } + + for (module in modules) { + val moduleBuilder = moduleToBuilder[module]!! + for (fragment in module.fragments) { + for (tag in fragment.nameBindings.keys) { + val fromModuleBuilder = definitionModule[tag] ?: continue // TODO error? + if (fromModuleBuilder == moduleBuilder) continue + + moduleBuilder.imports += CrossModuleRef(fromModuleBuilder, tag) + fromModuleBuilder.exports += tag + } + } + } + + return modules.associateWith { moduleToBuilder[it]!!.buildCrossModuleRefs() } + } + +} + +private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String) + + +private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, val relativeRequirePath: Boolean) { + val imports = mutableListOf() + val exports = mutableSetOf() + + private lateinit var exportNames: Map // tag -> name + + private fun buildUniqueNames() { + val names = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to sanitizeName(it.value.ident) } + val nameToCnt = mutableMapOf() + + val result = mutableMapOf() + + exports.sorted().forEach { tag -> + val suggestedName = names[tag] ?: error("Name not found for tag $tag") + val suffix = nameToCnt[suggestedName]?.let { "_$it" } ?: "" + nameToCnt[suggestedName] = nameToCnt.getOrDefault(suggestedName, 0) + 1 + result[tag] = suggestedName + suffix + } + + exportNames = result + } + + fun buildCrossModuleRefs(): CrossModuleReferences { + buildUniqueNames() + + val importedModules = mutableMapOf() + + fun JsIrModule.import(): JsName { + return importedModules.getOrPut(this) { + val moduleName = JsName(moduleName, false) + JsImportedModule(externalModuleName, moduleName, null, relativeRequirePath) + }.internalName + } + + val tagToName = module.fragments.flatMap { it.nameBindings.entries}.associate { it.key to it.value } + + val resultImports = imports.associate { + + val tag = it.tag + val exportedAs = it.module.exportNames[tag]!! + val importedAs = tagToName[tag]!! + val moduleName = it.module.module.import() + + val importStatement = JsVars.JsVar(importedAs, JsNameRef(exportedAs, JsNameRef("\$crossModule\$", moduleName.makeRef()))) + + tag to importStatement + } + + return CrossModuleReferences(importedModules.values.toList(), resultImports, exportNames) + } +} + +class CrossModuleReferences( + val importedModules: List, // additional Kotlin imported modules + val imports: Map, // tag -> import statement + val exports: Map, // tag -> name ) { - - private val importStatements = mutableMapOf() - private val importedModulesMap = mutableMapOf() - - private fun linkJsNames() { - val nameMap = mutableMapOf() - - fragments.flatMap { it }.forEach { f -> - f.buildRenames(nameMap).run { - rename(f.declarations) - rename(f.exports) - - f.imports.entries.forEach { (declaration, importExpression) -> - val importName = nameMap[declaration] ?: error("Missing name for declaration '${declaration}'") - importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression)))) - } - - val classModels = mutableMapOf() + f.classes - f.classes.clear() - classModels.entries.forEach { (name, model) -> - f.classes[rename(name)] = JsIrIcClassModel(model.superClasses.map { rename(it) }).also { - it.preDeclarationBlock.statements += model.preDeclarationBlock.statements - it.postDeclarationBlock.statements += model.postDeclarationBlock.statements - rename(it.preDeclarationBlock) - rename(it.postDeclarationBlock) - } - } - - rename(f.initializers) - f.mainFunction?.let { rename(it) } - f.testFunInvocation?.let { rename(it) } - f.suiteFn?.let { f.suiteFn = rename(it) } - } - } - } - - private fun JsIrProgramFragment.buildRenames(nameMap: MutableMap): Map { - val result = mutableMapOf() - - this.importedModules.forEach { module -> - val existingModule = importedModulesMap.getOrPut(module.key) { module } - if (existingModule !== module) { - result[module.internalName] = existingModule.internalName - } - } - - this.nameBindings.entries.forEach { (tag, name) -> - val existingName = nameMap.getOrPut(tag) { name } - if (existingName !== name) { - result[name] = existingName - } - } - - return result - } - - private fun Map.rename(name: JsName): JsName = getOrElse(name) { name } - - private fun Map.rename(rootNode: T): T { - rootNode.accept(object : RecursiveJsVisitor() { - override fun visitElement(node: JsNode) { - super.visitElement(node) - if (node is HasName) { - node.name = node.name?.let { rename(it) } - } - } - }) - return rootNode - } - - fun merge(): JsProgram { - linkJsNames() - - val moduleBody = mutableListOf().also { - if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt() - } - - val preDeclarationBlock = JsGlobalBlock() - val postDeclarationBlock = JsGlobalBlock() - - moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock) - - val classModels = mutableMapOf() - val initializerBlock = JsGlobalBlock() - fragments.forEach { - it.forEach { - moduleBody += it.declarations.statements - classModels += it.classes - initializerBlock.statements += it.initializers.statements - } - } - - // sort member forwarding code - processClassModels(classModels, preDeclarationBlock, postDeclarationBlock) - - moduleBody.addWithComment("block: post-declaration", postDeclarationBlock.statements) - moduleBody.addWithComment("block: init", initializerBlock.statements) - - val lastModuleFragments = fragments.last() - - // Merge test function invocations - if (lastModuleFragments.any { it.testFunInvocation != null }) { - val testFunBody = JsBlock() - val testFun = JsFunction(emptyScope, testFunBody, "root test fun") - val suiteFunRef = lastModuleFragments.firstNotNullOf { it.suiteFn }.makeRef() - - val tests = lastModuleFragments.filter { it.testFunInvocation != null } - .groupBy({ it.packageFqn }) { it.testFunInvocation } // String -> [IrSimpleFunction] - - for ((pkg, testCalls) in tests) { - val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg") - pkgTestFun.body.statements += testCalls - testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt() - } - - moduleBody.startRegion("block: tests") - moduleBody += JsInvocation(testFun).makeStmt() - moduleBody.endRegion() - } - - val callToMain = lastModuleFragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction } - - val exportStatements = fragments.flatMap { it.flatMap { it.exports.statements } } - - val importedJsModules = this.importedModulesMap.values.toList() - val importStatements = this.importStatements.values.toList() - - val internalModuleName = JsName("_", false) - - val program = JsProgram() - - if (generateScriptModule) { - with(program.globalBlock) { - this.statements.addWithComment("block: imports", importStatements) - this.statements += moduleBody - this.statements.addWithComment("block: exports", exportStatements) - } - } else { - val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply { - parameters += JsParameter(internalModuleName) - parameters += (importedJsModules).map { JsParameter(it.internalName) } - with(body) { - this.statements.addWithComment("block: imports", importStatements) - this.statements += moduleBody - this.statements.addWithComment("block: exports", exportStatements) - callToMain?.let { this.statements += it } - this.statements += JsReturn(internalModuleName.makeRef()) - } - } - - program.globalBlock.statements += ModuleWrapperTranslation.wrap( - moduleName, - rootFunction, - importedJsModules, - program, - kind = moduleKind - ) - } - - return program - } - - - private fun processClassModels( - classModelMap: Map, - preDeclarationBlock: JsBlock, - postDeclarationBlock: JsBlock - ) { - val declarationHandler = object : DFS.AbstractNodeHandler() { - override fun result() {} - override fun afterChildren(current: JsName) { - classModelMap[current]?.let { - preDeclarationBlock.statements += it.preDeclarationBlock.statements - postDeclarationBlock.statements += it.postDeclarationBlock.statements - } - } - } - - DFS.dfs( - classModelMap.keys, - { klass -> classModelMap[klass]?.superClasses ?: emptyList() }, - declarationHandler - ) - } - - private fun MutableList.startRegion(description: String = "") { - if (generateRegionComments) { - this += JsSingleLineComment("region $description") - } - } - - private fun MutableList.endRegion() { - if (generateRegionComments) { - this += JsSingleLineComment("endregion") - } - } - - private fun MutableList.addWithComment(regionDescription: String = "", block: JsBlock) { - startRegion(regionDescription) - this += block - endRegion() - } - - private fun MutableList.addWithComment(regionDescription: String = "", statements: List) { - if (statements.isEmpty()) return - - startRegion(regionDescription) - this += statements - endRegion() + companion object { + val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap()) } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt new file mode 100644 index 00000000000..56f2ff53e69 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -0,0 +1,256 @@ +/* + * Copyright 2010-2021 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.transformers.irToJs + +import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.utils.DFS + +class Merger( + private val moduleName: String, + private val moduleKind: ModuleKind, + private val fragments: List, + private val crossModuleReferences: CrossModuleReferences, + private val generateScriptModule: Boolean, + private val generateRegionComments: Boolean, + private val generateCallToMain: Boolean, +) { + + private val importStatements = mutableMapOf() + private val importedModulesMap = mutableMapOf() + + private val additionalExports = mutableListOf() + + private fun linkJsNames() { + val nameMap = mutableMapOf() + + fragments.forEach { f -> + f.buildRenames(nameMap).run { + rename(f.declarations) + rename(f.exports) + + f.imports.entries.forEach { (declaration, importExpression) -> + val importName = nameMap[declaration] ?: error("Missing name for declaration '${declaration}'") + importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression)))) + } + + val classModels = mutableMapOf() + f.classes + f.classes.clear() + classModels.entries.forEach { (name, model) -> + f.classes[rename(name)] = JsIrIcClassModel(model.superClasses.map { rename(it) }).also { + it.preDeclarationBlock.statements += model.preDeclarationBlock.statements + it.postDeclarationBlock.statements += model.postDeclarationBlock.statements + rename(it.preDeclarationBlock) + rename(it.postDeclarationBlock) + } + } + + rename(f.initializers) + f.mainFunction?.let { rename(it) } + f.testFunInvocation?.let { rename(it) } + f.suiteFn?.let { f.suiteFn = rename(it) } + } + } + + for ((tag, jsVar) in crossModuleReferences.imports) { + val importName = nameMap[tag] ?: error("Missing name for declaration '$tag'") + + importStatements.putIfAbsent(tag, JsVars(JsVars.JsVar(importName, jsVar.initExpression))) + } + + if (crossModuleReferences.exports.isNotEmpty()) { + val internalModuleName = JsName("_", false) + + val createExportBlock = jsAssignment( + JsNameRef("\$crossModule\$", internalModuleName.makeRef()), + JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral()) + ).makeStmt() + additionalExports += createExportBlock + + crossModuleReferences.exports.entries.forEach { (tag, name) -> + val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'") + additionalExports += jsAssignment( + JsNameRef(name, JsNameRef("\$crossModule\$", JsName("_", false).makeRef())), + JsNameRef(internalName) + ).makeStmt() + } + } + } + + private fun JsIrProgramFragment.buildRenames(nameMap: MutableMap): Map { + val result = mutableMapOf() + + this.importedModules.forEach { module -> + val existingModule = importedModulesMap.getOrPut(module.key) { module } + if (existingModule !== module) { + result[module.internalName] = existingModule.internalName + } + } + + this.nameBindings.entries.forEach { (tag, name) -> + val existingName = nameMap.getOrPut(tag) { name } + if (existingName !== name) { + result[name] = existingName + } + } + + return result + } + + private fun Map.rename(name: JsName): JsName = getOrElse(name) { name } + + private fun Map.rename(rootNode: T): T { + rootNode.accept(object : RecursiveJsVisitor() { + override fun visitElement(node: JsNode) { + super.visitElement(node) + if (node is HasName) { + node.name = node.name?.let { rename(it) } + } + } + }) + return rootNode + } + + fun merge(): JsProgram { + linkJsNames() + + val moduleBody = mutableListOf().also { + if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt() + } + + val preDeclarationBlock = JsGlobalBlock() + val postDeclarationBlock = JsGlobalBlock() + + moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock) + + val classModels = mutableMapOf() + val initializerBlock = JsGlobalBlock() + fragments.forEach { + moduleBody += it.declarations.statements + classModels += it.classes + initializerBlock.statements += it.initializers.statements + } + + // sort member forwarding code + processClassModels(classModels, preDeclarationBlock, postDeclarationBlock) + + moduleBody.addWithComment("block: post-declaration", postDeclarationBlock.statements) + moduleBody.addWithComment("block: init", initializerBlock.statements) + + // Merge test function invocations + if (fragments.any { it.testFunInvocation != null }) { + val testFunBody = JsBlock() + val testFun = JsFunction(emptyScope, testFunBody, "root test fun") + val suiteFunRef = fragments.firstNotNullOf { it.suiteFn }.makeRef() + + val tests = fragments.filter { it.testFunInvocation != null } + .groupBy({ it.packageFqn }) { it.testFunInvocation } // String -> [IrSimpleFunction] + + for ((pkg, testCalls) in tests) { + val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg") + pkgTestFun.body.statements += testCalls + testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt() + } + + moduleBody.startRegion("block: tests") + moduleBody += JsInvocation(testFun).makeStmt() + moduleBody.endRegion() + } + + val callToMain = fragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction } + + val exportStatements = fragments.flatMap { it.exports.statements } + additionalExports + + val importedJsModules = this.importedModulesMap.values.toList() + this.crossModuleReferences.importedModules + val importStatements = this.importStatements.values.toList() + + val internalModuleName = JsName("_", false) + + val program = JsProgram() + + if (generateScriptModule) { + with(program.globalBlock) { + this.statements.addWithComment("block: imports", importStatements) + this.statements += moduleBody + this.statements.addWithComment("block: exports", exportStatements) + } + } else { + val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply { + parameters += JsParameter(internalModuleName) + parameters += (importedJsModules).map { JsParameter(it.internalName) } + with(body) { + this.statements.addWithComment("block: imports", importStatements) + this.statements += moduleBody + this.statements.addWithComment("block: exports", exportStatements) + if (generateCallToMain) { + callToMain?.let { this.statements += it } + } + this.statements += JsReturn(internalModuleName.makeRef()) + } + } + + program.globalBlock.statements += ModuleWrapperTranslation.wrap( + moduleName, + rootFunction, + importedJsModules, + program, + kind = moduleKind + ) + } + + return program + } + + + private fun processClassModels( + classModelMap: Map, + preDeclarationBlock: JsBlock, + postDeclarationBlock: JsBlock + ) { + val declarationHandler = object : DFS.AbstractNodeHandler() { + override fun result() {} + override fun afterChildren(current: JsName) { + classModelMap[current]?.let { + preDeclarationBlock.statements += it.preDeclarationBlock.statements + postDeclarationBlock.statements += it.postDeclarationBlock.statements + } + } + } + + DFS.dfs( + classModelMap.keys, + { klass -> classModelMap[klass]?.superClasses ?: emptyList() }, + declarationHandler + ) + } + + private fun MutableList.startRegion(description: String = "") { + if (generateRegionComments) { + this += JsSingleLineComment("region $description") + } + } + + private fun MutableList.endRegion() { + if (generateRegionComments) { + this += JsSingleLineComment("endregion") + } + } + + private fun MutableList.addWithComment(regionDescription: String = "", block: JsBlock) { + startRegion(regionDescription) + this += block + endRegion() + } + + private fun MutableList.addWithComment(regionDescription: String = "", statements: List) { + if (statements.isEmpty()) return + + startRegion(regionDescription) + this += statements + endRegion() + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt index b4dcbbadf46..931fe0591fc 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationOptions import org.jetbrains.kotlin.ir.backend.js.codegen.generateEsModules import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.util.IrMessageLogger import org.jetbrains.kotlin.ir.util.SymbolTable @@ -136,7 +137,7 @@ class JsIrBackendFacade( val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js") val dceOutputFile = File(JsEnvironmentConfigurator.getDceJsArtifactPath(testServices, module.name) + ".js") if (!esModules) { - val transformer = IrModuleToJsTransformer( + val transformer = IrModuleToJsTransformerTmp( loweredIr.context, mainArguments, fullJs = true, diff --git a/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt b/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt index 6322d5ec271..2f5be5fa176 100644 --- a/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt +++ b/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt @@ -20,6 +20,10 @@ inline fun buzz(): Int { return o.f() } +fun overloadedFun(i: Int) = i + 1 + +fun overloadedFun(s: String) = s + "!" + // FILE: lib.js function bar() { @@ -47,5 +51,11 @@ fun box(): String { val e = lib.callFoo() if (e != 23) return "fail: inline function calling another function: $e" + val f = lib.overloadedFun(1) + if (f != 2) return "fail: overloadedFun(Int): $f" + + val g = lib.overloadedFun("A") + if (g != "A!") return "fail: overloadedFun(String): $f" + return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/box/jsName/jsNamePropertyAccessors.kt b/js/js.translator/testData/box/jsName/jsNamePropertyAccessors.kt index c7c100eb248..9d88c81778d 100644 --- a/js/js.translator/testData/box/jsName/jsNamePropertyAccessors.kt +++ b/js/js.translator/testData/box/jsName/jsNamePropertyAccessors.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1285 -// IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS_IR_ES6 package foo