[JS IR] support per-module in the new Ir2JS
This commit is contained in:
committed by
TeamCityServer
parent
1f55dc73d2
commit
0506d422a3
@@ -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
|
||||
)
|
||||
}
|
||||
+3
-4
@@ -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)
|
||||
|
||||
+83
-15
@@ -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()
|
||||
|
||||
+99
-220
@@ -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())
|
||||
}
|
||||
}
|
||||
+256
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user