[JS IR] support per-module in the new Ir2JS

This commit is contained in:
Anton Bannykh
2021-10-25 13:20:39 +03:00
committed by TeamCityServer
parent 1f55dc73d2
commit 0506d422a3
8 changed files with 463 additions and 246 deletions
@@ -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<String, ModuleCache>
): 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
)
}
@@ -279,10 +279,9 @@ private fun <T> 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)
@@ -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<IrModuleFragment>,
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>,
): List<List<JsIrProgramFragment>> {
): JsIrProgram {
val fragments = mutableMapOf<IrFile, JsIrProgramFragment>()
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<List<JsIrProgramFragment>>
fragments: List<JsIrProgramFragment>,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty,
generateCallToMain: Boolean,
): CompilationOutputs {
val program = Merger(
moduleName,
moduleKind,
fragments,
false,
true
crossModuleReferences,
generateScriptModule = false,
generateRegionComments = true,
generateCallToMain,
).merge()
program.resolveTemporaryNames()
@@ -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<String, JsName>()
@@ -24,223 +20,106 @@ class JsIrProgramFragment(val packageFqn: String) {
var mainFunction: JsStatement? = null
var testFunInvocation: JsStatement? = null
var suiteFn: JsName? = null
val definitions = mutableSetOf<String>()
}
class Merger(
private val moduleName: String,
private val moduleKind: ModuleKind,
private val fragments: List<List<JsIrProgramFragment>>,
private val generateScriptModule: Boolean,
private val generateRegionComments: Boolean,
class JsIrModule(val moduleName: String, val externalModuleName: String, val fragments: List<JsIrProgramFragment>)
class JsIrProgram(val modules: List<JsIrModule>) {
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
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<CrossModuleRef>()
val exports = mutableSetOf<String>()
private lateinit var exportNames: Map<String, String> // tag -> name
private fun buildUniqueNames() {
val names = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to sanitizeName(it.value.ident) }
val nameToCnt = mutableMapOf<String, Int>()
val result = mutableMapOf<String, String>()
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<JsIrModule, JsImportedModule>()
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<JsImportedModule>, // additional Kotlin imported modules
val imports: Map<String, JsVars.JsVar>, // tag -> import statement
val exports: Map<String, String>, // tag -> name
) {
private val importStatements = mutableMapOf<String, JsStatement>()
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
private fun linkJsNames() {
val nameMap = mutableMapOf<String, JsName>()
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<JsName, JsIrIcClassModel>() + 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<String, JsName>): Map<JsName, JsName> {
val result = mutableMapOf<JsName, JsName>()
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<JsName, JsName>.rename(name: JsName): JsName = getOrElse(name) { name }
private fun <T : JsNode> Map<JsName, JsName>.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<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock)
val classModels = mutableMapOf<JsName, JsIrIcClassModel>()
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<JsName, JsIrIcClassModel>,
preDeclarationBlock: JsBlock,
postDeclarationBlock: JsBlock
) {
val declarationHandler = object : DFS.AbstractNodeHandler<JsName, Unit>() {
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<JsStatement>.startRegion(description: String = "") {
if (generateRegionComments) {
this += JsSingleLineComment("region $description")
}
}
private fun MutableList<JsStatement>.endRegion() {
if (generateRegionComments) {
this += JsSingleLineComment("endregion")
}
}
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", block: JsBlock) {
startRegion(regionDescription)
this += block
endRegion()
}
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", statements: List<JsStatement>) {
if (statements.isEmpty()) return
startRegion(regionDescription)
this += statements
endRegion()
companion object {
val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap())
}
}
@@ -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<JsIrProgramFragment>,
private val crossModuleReferences: CrossModuleReferences,
private val generateScriptModule: Boolean,
private val generateRegionComments: Boolean,
private val generateCallToMain: Boolean,
) {
private val importStatements = mutableMapOf<String, JsStatement>()
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
private val additionalExports = mutableListOf<JsStatement>()
private fun linkJsNames() {
val nameMap = mutableMapOf<String, JsName>()
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<JsName, JsIrIcClassModel>() + 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<String, JsName>): Map<JsName, JsName> {
val result = mutableMapOf<JsName, JsName>()
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<JsName, JsName>.rename(name: JsName): JsName = getOrElse(name) { name }
private fun <T : JsNode> Map<JsName, JsName>.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<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock)
val classModels = mutableMapOf<JsName, JsIrIcClassModel>()
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<JsName, JsIrIcClassModel>,
preDeclarationBlock: JsBlock,
postDeclarationBlock: JsBlock
) {
val declarationHandler = object : DFS.AbstractNodeHandler<JsName, Unit>() {
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<JsStatement>.startRegion(description: String = "") {
if (generateRegionComments) {
this += JsSingleLineComment("region $description")
}
}
private fun MutableList<JsStatement>.endRegion() {
if (generateRegionComments) {
this += JsSingleLineComment("endregion")
}
}
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", block: JsBlock) {
startRegion(regionDescription)
this += block
endRegion()
}
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", statements: List<JsStatement>) {
if (statements.isEmpty()) return
startRegion(regionDescription)
this += statements
endRegion()
}
}
@@ -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,
@@ -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"
}
@@ -1,5 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1285
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
package foo