[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.generateJsTests
|
||||||
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
|
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.lower.serialization.ir.JsIrLinker
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
|
||||||
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.utils.serialization.JsIrAstDeserializer
|
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||||
@@ -115,6 +113,13 @@ fun generateJsFromAst(
|
|||||||
caches: Map<String, ModuleCache>
|
caches: Map<String, ModuleCache>
|
||||||
): CompilerResult {
|
): CompilerResult {
|
||||||
val deserializer = JsIrAstDeserializer()
|
val deserializer = JsIrAstDeserializer()
|
||||||
val fragments = caches.values.map { it.asts.values.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it))} } }
|
val fragments = JsIrProgram(caches.values.map { JsIrModule(it.name, it.name, it.asts.values.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it))} }) })
|
||||||
return CompilerResult(generateWrappedModuleBody("main", ModuleKind.PLAIN, fragments), null)
|
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
|
} else null
|
||||||
|
|
||||||
private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) =
|
private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) =
|
||||||
correspondingProperty
|
correspondingProperty?.let {
|
||||||
?.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) != true &&
|
it.isForLazyInit() && !it.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol)
|
||||||
withPersistentSafe { origin in compatibleOrigins } == true
|
} ?: true && withPersistentSafe { origin in compatibleOrigins } == true
|
||||||
|
|
||||||
|
|
||||||
private fun IrDeclaration.assertCompatibleDeclaration() {
|
private fun IrDeclaration.assertCompatibleDeclaration() {
|
||||||
assert(this !is PersistentIrElementBase<*> || this.createdOn <= this.factory.stageController.currentStage)
|
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) }
|
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) {
|
val dceJsCode = if (dceJs) {
|
||||||
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
|
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
|
||||||
|
|
||||||
generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData))
|
generateWrappedModuleBody(
|
||||||
|
multiModule,
|
||||||
|
mainModuleName,
|
||||||
|
moduleKind,
|
||||||
|
generateProgramFragments(modules, exportData),
|
||||||
|
relativeRequirePath
|
||||||
|
)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
return CompilerResult(jsCode, dceJsCode, dts)
|
return CompilerResult(jsCode, dceJsCode, dts)
|
||||||
@@ -98,20 +110,23 @@ class IrModuleToJsTransformerTmp(
|
|||||||
return additionalExports + exports
|
return additionalExports + exports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrModuleFragment.externalModuleName(): String {
|
||||||
|
return moduleToName[this] ?: sanitizeName(safeName)
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateProgramFragments(
|
private fun generateProgramFragments(
|
||||||
modules: Iterable<IrModuleFragment>,
|
modules: Iterable<IrModuleFragment>,
|
||||||
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>,
|
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>,
|
||||||
): List<List<JsIrProgramFragment>> {
|
): JsIrProgram {
|
||||||
|
|
||||||
val fragments = mutableMapOf<IrFile, JsIrProgramFragment>()
|
return JsIrProgram(
|
||||||
modules.forEach { m ->
|
modules.map { m ->
|
||||||
m.files.forEach { f ->
|
JsIrModule(m.safeName, m.externalModuleName(), m.files.map { f ->
|
||||||
val exports = exportData[m]!![f]!! // TODO
|
val exports = exportData[m]!![f]!!
|
||||||
fragments[f] = generateProgramFragment(f, exports)
|
generateProgramFragment(f, exports)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
return modules.map { it.files.map { fragments[it]!! } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
|
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
|
||||||
@@ -213,6 +228,10 @@ class IrModuleToJsTransformerTmp(
|
|||||||
result.imports[tag] = importExpression
|
result.imports[tag] = importExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.declarations.forEach {
|
||||||
|
result.definitions += computeTag(it)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
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,
|
moduleName: String,
|
||||||
moduleKind: ModuleKind,
|
moduleKind: ModuleKind,
|
||||||
fragments: List<List<JsIrProgramFragment>>
|
fragments: List<JsIrProgramFragment>,
|
||||||
|
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty,
|
||||||
|
generateCallToMain: Boolean,
|
||||||
): CompilationOutputs {
|
): CompilationOutputs {
|
||||||
val program = Merger(
|
val program = Merger(
|
||||||
moduleName,
|
moduleName,
|
||||||
moduleKind,
|
moduleKind,
|
||||||
fragments,
|
fragments,
|
||||||
false,
|
crossModuleReferences,
|
||||||
true
|
generateScriptModule = false,
|
||||||
|
generateRegionComments = true,
|
||||||
|
generateCallToMain,
|
||||||
).merge()
|
).merge()
|
||||||
|
|
||||||
program.resolveTemporaryNames()
|
program.resolveTemporaryNames()
|
||||||
|
|||||||
+99
-220
@@ -5,12 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||||
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
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) {
|
class JsIrProgramFragment(val packageFqn: String) {
|
||||||
val nameBindings = mutableMapOf<String, JsName>()
|
val nameBindings = mutableMapOf<String, JsName>()
|
||||||
@@ -24,223 +20,106 @@ class JsIrProgramFragment(val packageFqn: String) {
|
|||||||
var mainFunction: JsStatement? = null
|
var mainFunction: JsStatement? = null
|
||||||
var testFunInvocation: JsStatement? = null
|
var testFunInvocation: JsStatement? = null
|
||||||
var suiteFn: JsName? = null
|
var suiteFn: JsName? = null
|
||||||
|
val definitions = mutableSetOf<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Merger(
|
class JsIrModule(val moduleName: String, val externalModuleName: String, val fragments: List<JsIrProgramFragment>)
|
||||||
private val moduleName: String,
|
|
||||||
private val moduleKind: ModuleKind,
|
class JsIrProgram(val modules: List<JsIrModule>) {
|
||||||
private val fragments: List<List<JsIrProgramFragment>>,
|
|
||||||
private val generateScriptModule: Boolean,
|
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
|
||||||
private val generateRegionComments: Boolean,
|
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
|
||||||
) {
|
) {
|
||||||
|
companion object {
|
||||||
private val importStatements = mutableMapOf<String, JsStatement>()
|
val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap())
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.codegen.generateEsModules
|
||||||
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
|
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.IrModuleToJsTransformer
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||||
import org.jetbrains.kotlin.ir.util.IrMessageLogger
|
import org.jetbrains.kotlin.ir.util.IrMessageLogger
|
||||||
import org.jetbrains.kotlin.ir.util.SymbolTable
|
import org.jetbrains.kotlin.ir.util.SymbolTable
|
||||||
@@ -136,7 +137,7 @@ class JsIrBackendFacade(
|
|||||||
val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js")
|
val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js")
|
||||||
val dceOutputFile = File(JsEnvironmentConfigurator.getDceJsArtifactPath(testServices, module.name) + ".js")
|
val dceOutputFile = File(JsEnvironmentConfigurator.getDceJsArtifactPath(testServices, module.name) + ".js")
|
||||||
if (!esModules) {
|
if (!esModules) {
|
||||||
val transformer = IrModuleToJsTransformer(
|
val transformer = IrModuleToJsTransformerTmp(
|
||||||
loweredIr.context,
|
loweredIr.context,
|
||||||
mainArguments,
|
mainArguments,
|
||||||
fullJs = true,
|
fullJs = true,
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ inline fun buzz(): Int {
|
|||||||
return o.f()
|
return o.f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun overloadedFun(i: Int) = i + 1
|
||||||
|
|
||||||
|
fun overloadedFun(s: String) = s + "!"
|
||||||
|
|
||||||
// FILE: lib.js
|
// FILE: lib.js
|
||||||
|
|
||||||
function bar() {
|
function bar() {
|
||||||
@@ -47,5 +51,11 @@ fun box(): String {
|
|||||||
val e = lib.callFoo()
|
val e = lib.callFoo()
|
||||||
if (e != 23) return "fail: inline function calling another function: $e"
|
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"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1285
|
// EXPECTED_REACHABLE_NODES: 1285
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR_ES6
|
// IGNORE_BACKEND: JS_IR_ES6
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user