From 94ed6a1dbd2239c57eb0bc4c5b401fc3d862c263 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Fri, 1 Oct 2021 19:03:25 +0300 Subject: [PATCH] [JS IR] bring back old ir2js to work alongside with the new one Should be removed when we're sure the new one is OK --- .../kotlin/ir/backend/js/compilerWithIC.kt | 3 +- .../irToJs/IrModuleToJsTransformer.kt | 498 ++++++++++++------ .../irToJs/IrModuleToJsTransformerTmp.kt | 253 +++++++++ .../transformers/irToJs/JsClassGenerator.kt | 11 +- .../irToJs/JsIrProgramFragment.kt | 10 +- .../serialization/JsIrAstDeserializer.kt | 5 +- .../utils/serialization/JsIrAstSerializer.kt | 3 +- 7 files changed, 599 insertions(+), 184 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt index db8b2848219..9d2842cd4c4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt @@ -15,6 +15,7 @@ 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.utils.serialization.JsIrAstDeserializer import org.jetbrains.kotlin.ir.declarations.IrFactory @@ -88,7 +89,7 @@ fun compileWithIC( jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, allModules) - val transformer = IrModuleToJsTransformer( + val transformer = IrModuleToJsTransformerTmp( context, mainArguments, fullJs = generateFullJs, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index b70e19ed052..07fd79379a2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -5,17 +5,18 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs -import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations -import org.jetbrains.kotlin.ir.backend.js.export.* +import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator +import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements +import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule +import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering import org.jetbrains.kotlin.ir.backend.js.utils.* -import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer -import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstSerializer import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isInterface import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor @@ -28,9 +29,7 @@ import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer import org.jetbrains.kotlin.js.util.TextOutputImpl -import org.jetbrains.kotlin.serialization.js.ast.JsAstSerializer -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import org.jetbrains.kotlin.utils.DFS import java.io.File class IrModuleToJsTransformer( @@ -47,201 +46,318 @@ class IrModuleToJsTransformer( ) { private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS) - private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! - private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! - - fun generateBinaryAst(files: Iterable): Map { - val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) - - val exportData = files.associate { file -> - file to exportModelGenerator.generateExport(file) - } - - files.forEach { StaticMembersLowering(backendContext).lower(it) } - - return generateBinaryAst(files, exportData) - } - fun generateModule(modules: Iterable): CompilerResult { - val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) - - val exportData = modules.associate { module -> - module to module.files.associate { file -> - file to exportModelGenerator.generateExport(file) - } + val additionalPackages = with(backendContext) { + externalPackageFragment.values + listOf( + bodilessBuiltInsPackageFragment, + ) + packageLevelJsModules } - val dts = wrapTypeScript(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatMap { it } }.toTypeScript(moduleKind)) + val moduleKind: ModuleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! + val exportedModule = ExportModelGenerator(backendContext, generateNamespacesForPackages = true).generateExport(modules, moduleKind = moduleKind) + val dts = exportedModule.toTypeScript() modules.forEach { module -> module.files.forEach { StaticMembersLowering(backendContext).lower(it) } } - val jsCode = if (fullJs) generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) else null + modules.forEach { module -> + namer.merge(module.files, additionalPackages) + } + + val jsCode = if (fullJs) generateWrappedModuleBody(modules, exportedModule, namer) else null val dceJsCode = if (dceJs) { eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects) - - generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) + // Use a fresh namer for DCE so that we could compare the result with DCE-driven + // TODO: is this mode relevant for scripting? If yes, refactor so that the external name tables are used here when needed. + val namer = NameTables(emptyList(), context = backendContext) + namer.merge(modules.flatMap { it.files }, additionalPackages) + generateWrappedModuleBody(modules, exportedModule, namer) } else null return CompilerResult(jsCode, dceJsCode, dts) } - private fun generateBinaryAst( - files: Iterable, - exportData: Map>, - ): Map { + private fun generateWrappedModuleBody(modules: Iterable, exportedModule: ExportedModule, namer: NameTables): CompilationOutputs { + if (multiModule) { - val serializer = JsIrAstSerializer() + val refInfo = buildCrossModuleReferenceInfo(modules) - val result = mutableMapOf() - files.forEach { f -> - val exports = exportData[f]!! // TODO - val fragment = generateProgramFragment(f, exports) - val output = ByteArrayOutputStream() - serializer.serialize(fragment, output) - val binaryAst = output.toByteArray() - result[f.fileEntry.name] = binaryAst + val rM = modules.reversed() + + val main = rM.first() + val others = rM.drop(1) + + val mainModule = generateWrappedModuleBody2( + listOf(main), + others, + exportedModule, + namer, + refInfo, + generateMainCall = true + ) + + val dependencies = others.mapIndexed { index, module -> + val moduleName = module.externalModuleName() + + val exportedDeclarations = ExportModelGenerator(backendContext, generateNamespacesForPackages = true).let { module.files.flatMap { file -> it.generateExport(file) } } + + moduleName to generateWrappedModuleBody2( + listOf(module), + others.drop(index + 1), + ExportedModule(moduleName, exportedModule.moduleKind, exportedDeclarations), + namer, + refInfo, + generateMainCall = false + ) + }.reversed() + + return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies) + } else { + return generateWrappedModuleBody2( + modules, + emptyList(), + exportedModule, + namer, + EmptyCrossModuleReferenceInfo + ) } - - return result } - - private fun generateProgramFragments( + private fun generateWrappedModuleBody2( modules: Iterable, - exportData: Map>>, - ): List> { - - val fragments = mutableMapOf() - modules.forEach { m -> - m.files.forEach { f -> - val exports = exportData[m]!![f]!! // TODO - fragments[f] = generateProgramFragment(f, exports) - } - } - -// // TODO remove serialization -> deserialization work -// val serialized = mutableListOf>() -// -// val serializer = JsIrAstSerializer() -// fragments.entries.forEach { (file, fragment) -> -// val output = ByteArrayOutputStream() -// serializer.serialize(fragment, output) -// val binaryAst = output.toByteArray() -// serialized += file to binaryAst -// } -// -// val restoredMap = mutableMapOf() -// -// val deserializer = JsIrAstDeserializer() -// -// serialized.forEach { (file, binaryAst) -> -// restoredMap[file] = deserializer.deserialize(ByteArrayInputStream(binaryAst)) -// } - - return modules.map { it.files.map { fragments[it]!! } } - } - - private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH) - private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP) - - private fun generateProgramFragment(file: IrFile, exports: List): JsIrProgramFragment { - val nameGenerator = JsNameLinkingNamer(backendContext) + dependencies: Iterable, + exportedModule: ExportedModule, + namer: NameTables, + refInfo: CrossModuleReferenceInfo, + generateMainCall: Boolean = true + ): CompilationOutputs { + val nameGenerator = refInfo.withReferenceTracking( + IrNamerImpl(newNameTables = namer, backendContext), + modules + ) val staticContext = JsStaticContext( backendContext = backendContext, irNamer = nameGenerator, globalNameScope = namer.globalNames ) - val result = JsIrProgramFragment(file.fqName.asString()) + val (importStatements, importedJsModules) = + generateImportStatements( + getNameForExternalDeclaration = { staticContext.getNameForStaticDeclaration(it) }, + declareFreshGlobal = { JsName(sanitizeName(it), false) } // TODO: Declare fresh name + ) + + val moduleBody = generateModuleBody(modules, staticContext) val internalModuleName = JsName("_", false) val globalNames = NameTable(namer.globalNames) - val exportStatements = ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport( - ExportedModule(mainModuleName, moduleKind, exports), - internalModuleName, + val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) } + .generateModuleExport(exportedModule, internalModuleName) + + val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it), false) }) + val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName) + + val program = JsProgram() + if (generateScriptModule) { + with(program.globalBlock) { + statements.addWithComment("block: imports", importStatements + crossModuleImports) + statements += moduleBody + statements.addWithComment("block: exports", exportStatements + crossModuleExports) + } + } else { + val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply { + parameters += JsParameter(internalModuleName) + parameters += (importedJsModules + importedKotlinModules).map { JsParameter(it.internalName) } + with(body) { + statements.addWithComment("block: imports", importStatements + crossModuleImports) + statements += moduleBody + statements.addWithComment("block: exports", exportStatements + crossModuleExports) + if (generateMainCall) { + statements += generateCallToMain(modules, staticContext) + } + statements += JsReturn(internalModuleName.makeRef()) + } + } + + program.globalBlock.statements += ModuleWrapperTranslation.wrap( + exportedModule.name, + rootFunction, + importedJsModules + importedKotlinModules, + program, + kind = exportedModule.moduleKind + ) + } + + val jsCode = TextOutputImpl() + + val configuration = backendContext.configuration + val sourceMapPrefix = configuration.get(JSConfigurationKeys.SOURCE_MAP_PREFIX, "") + val sourceMapsEnabled = configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP) + + val sourceMapBuilder = SourceMap3Builder(null, jsCode, sourceMapPrefix) + val sourceMapBuilderConsumer = + if (sourceMapsEnabled) { + val sourceRoots = configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, emptyList()).map(::File) + val generateRelativePathsInSourceMap = sourceMapPrefix.isEmpty() && sourceRoots.isEmpty() + val outputDir = if (generateRelativePathsInSourceMap) configuration.get(JSConfigurationKeys.OUTPUT_DIR) else null + + val pathResolver = SourceFilePathResolver(sourceRoots, outputDir) + + val sourceMapContentEmbedding = + configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING) + + SourceMapBuilderConsumer( + File("."), + sourceMapBuilder, + pathResolver, + sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS, + sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER + ) + } else { + null + } + + program.accept(JsToStringGenerationVisitor(jsCode, sourceMapBuilderConsumer ?: NoOpSourceLocationConsumer)) + + return CompilationOutputs( + jsCode.toString(), + program, + if(sourceMapsEnabled) sourceMapBuilder.build() else null ) + } - result.exports.statements += exportStatements + private fun IrModuleFragment.externalModuleName(): String { + return moduleToName[this] ?: sanitizeName(safeName) + } - if (exports.isNotEmpty()) { - result.dts = exports.toTypeScript(moduleKind) - } + private fun generateCrossModuleImports( + namerWithImports: IrNamerWithImports, + currentModules: Iterable, + allowedDependencies: Iterable, + declareFreshGlobal: (String) -> JsName + ): Pair, List> { + val imports = mutableListOf() + val modules = mutableListOf() - val statements = result.declarations.statements - - - val fileStatements = file.accept(IrFileToJsTransformer(), staticContext).statements - if (fileStatements.isNotEmpty()) { - var startComment = "" - - if (generateRegionComments) { - startComment = "region " + namerWithImports.imports().forEach { (module, names) -> + check(module in allowedDependencies) { + val deps = if (names.size > 10) "[${names.take(10).joinToString()}, ...]" else "$names" + "Module ${currentModules.map { it.name.asString() }} depend on module ${module.name.asString()} via $deps" } - if (generateRegionComments || generateFilePaths) { - val originalPath = file.path - val path = pathPrefixMap.entries - .find { (k, _) -> originalPath.startsWith(k) } - ?.let { (k, v) -> v + originalPath.substring(k.length) } - ?: originalPath + val moduleName = declareFreshGlobal(module.safeName) + modules += JsImportedModule(module.externalModuleName(), moduleName, null, relativeRequirePath) - startComment += "file: $path" - } - - if (startComment.isNotEmpty()) { - statements.add(JsSingleLineComment(startComment)) - } - - statements.addAll(fileStatements) - if (generateRegionComments) { - statements += JsSingleLineComment("endregion") + names.forEach { + imports += JsVars(JsVars.JsVar(JsName(it, false), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef())))) } } - staticContext.classModels.entries.forEach { (symbol, model) -> - result.classes[nameGenerator.getNameForClass(symbol.owner)] = model + return imports to modules + } + + private fun generateCrossModuleExports( + modules: Iterable, + refInfo: CrossModuleReferenceInfo, + internalModuleName: JsName + ): List { + return modules.flatMap { + refInfo.exports(it).map { + jsAssignment( + JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())), + JsNameRef(it) + ).makeStmt() + } + }.let { + if (!it.isEmpty()) { + val createExportBlock = jsAssignment( + JsNameRef("\$crossModule\$", internalModuleName.makeRef()), + JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral()) + ).makeStmt() + return listOf(createExportBlock) + it + } else it + } + } + + private fun generateModuleBody(modules: Iterable, staticContext: JsStaticContext): List { + val statements = mutableListOf().also { + if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt() } - result.initializers.statements += staticContext.initializerBlock.statements + val preDeclarationBlock = JsGlobalBlock() + val postDeclarationBlock = JsGlobalBlock() - if (mainArguments != null) { - JsMainFunctionDetector(backendContext).getMainFunctionOrNull(file)?.let { - val jsName = staticContext.getNameForStaticFunction(it) - val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false - val generateContinuation = it.isLoweredSuspendFunction(backendContext) - result.mainFunction = JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, staticContext)).makeStmt() + statements.addWithComment("block: pre-declaration", preDeclarationBlock) + + val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH) + val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP) + + modules.forEach { module -> + module.files.forEach { + val fileStatements = it.accept(IrFileToJsTransformer(), staticContext).statements + if (fileStatements.isNotEmpty()) { + var startComment = "" + + if (generateRegionComments) { + startComment = "region " + } + + if (generateRegionComments || generateFilePaths) { + val originalPath = it.path + val path = pathPrefixMap.entries + .find { (k, _) -> originalPath.startsWith(k) } + ?.let { (k, v) -> v + originalPath.substring(k.length) } + ?: originalPath + + startComment += "file: $path" + } + + if (startComment.isNotEmpty()) { + statements.add(JsSingleLineComment(startComment)) + } + + statements.addAll(fileStatements) + statements.endRegion() + } } } - backendContext.testFunsPerFile[file]?.let { - result.testFunInvocation = JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt() - result.suiteFn = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner) + // sort member forwarding code + processClassModels(staticContext.classModels, preDeclarationBlock, postDeclarationBlock) + + statements.addWithComment("block: post-declaration", postDeclarationBlock.statements) + statements.addWithComment("block: init", staticContext.initializerBlock.statements) + + modules.forEach { module -> + val tests = module.files + .groupBy({ it.fqName.asString()}) { backendContext.testFunsPerFile[it] } + .mapNotNull{ (fqn, testFuns) -> testFuns.filterNotNull().let { if (it.isEmpty()) null else fqn to it } } + .associate { it } + + if (tests.isNotEmpty()) { + val testFunBody = JsBlock() + val testFun = JsFunction(emptyScope, testFunBody, "root test fun") + val suiteFunRef = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner).makeRef() + + for ((pkg, testFuns) in tests) { + val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg") + pkgTestFun.body.statements += testFuns.map { + JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt() + } + testFun.body.statements += + JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt() + } + + statements.startRegion("block: tests") + statements += JsInvocation(testFun).makeStmt() + statements.endRegion() + } } - - - result.importedModules += nameGenerator.importedModules - - fun computeTag(declaration: IrDeclaration): String { - // TODO proper tags - return System.identityHashCode(declaration).toString() - } - - nameGenerator.nameMap.entries.forEach { (declaration, name) -> - val tag = computeTag(declaration) - result.nameBindings[tag] = name - } - - nameGenerator.imports.entries.forEach { (declaration, importExpression) -> - val tag = computeTag(declaration) - result.imports[tag] = importExpression - } - - return result + return statements } private fun generateMainArguments( @@ -263,6 +379,18 @@ class IrModuleToJsTransformer( return listOfNotNull(mainArgumentsArray, continuation) } + private fun generateCallToMain(modules: Iterable, staticContext: JsStaticContext): List { + // TODO: Generate calls to main as IR->IR lowering + if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists + val mainFunction = JsMainFunctionDetector(backendContext).getMainFunctionOrNull(modules.last()) + return mainFunction?.let { + val jsName = staticContext.getNameForStaticFunction(it) + val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false + val generateContinuation = it.isLoweredSuspendFunction(backendContext) + listOf(JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, staticContext)).makeStmt()) + } ?: emptyList() + } + private fun generateImportStatements( getNameForExternalDeclaration: (IrDeclarationWithName) -> JsName, declareFreshGlobal: (String) -> JsName @@ -313,29 +441,53 @@ class IrModuleToJsTransformer( val importedJsModules = (declarationLevelJsModules + packageLevelJsModules).distinctBy { it.key } return Pair(importStatements, importedJsModules) } + + + private fun MutableList.startRegion(description: String = "") { + if (generateRegionComments) { + this += JsSingleLineComment("region $description") + } + } + + private fun MutableList.endRegion() { + if (generateRegionComments) { + this += JsSingleLineComment("endregion") + } + } + + private fun MutableList.addWithComment(regionDescription: String = "", block: JsBlock) { + startRegion(regionDescription) + this += block + endRegion() + } + + private fun MutableList.addWithComment(regionDescription: String = "", statements: List) { + if (statements.isEmpty()) return + + startRegion(regionDescription) + this += statements + endRegion() + } } -fun generateWrappedModuleBody( - moduleName: String, - moduleKind: ModuleKind, - fragments: List> -): CompilationOutputs { - val program = Merger( - moduleName, - moduleKind, - fragments, - false, - true - ).merge() +fun processClassModels( + classModelMap: Map, + preDeclarationBlock: JsBlock, + postDeclarationBlock: JsBlock +) { + val declarationHandler = object : DFS.AbstractNodeHandler() { + override fun result() {} + override fun afterChildren(current: IrClassSymbol) { + classModelMap[current]?.let { + preDeclarationBlock.statements += it.preDeclarationBlock.statements + postDeclarationBlock.statements += it.postDeclarationBlock.statements + } + } + } - program.resolveTemporaryNames() - - val jsCode = TextOutputImpl() - - program.accept(JsToStringGenerationVisitor(jsCode, NoOpSourceLocationConsumer)) - - return CompilationOutputs( - jsCode.toString(), - null + DFS.dfs( + classModelMap.keys, + { klass -> classModelMap[klass]?.superClasses ?: emptyList() }, + declarationHandler ) } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt new file mode 100644 index 00000000000..f4360abe169 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt @@ -0,0 +1,253 @@ +/* + * 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.config.CommonConfigurationKeys +import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs +import org.jetbrains.kotlin.ir.backend.js.CompilerResult +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations +import org.jetbrains.kotlin.ir.backend.js.export.* +import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering +import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstSerializer +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.types.classifierOrFail +import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor +import org.jetbrains.kotlin.js.backend.NoOpSourceLocationConsumer +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.util.TextOutputImpl +import org.jetbrains.kotlin.serialization.js.ModuleKind +import java.io.ByteArrayOutputStream + +class IrModuleToJsTransformerTmp( + private val backendContext: JsIrBackendContext, + private val mainArguments: List?, + private val generateScriptModule: Boolean = false, + var namer: NameTables = NameTables(emptyList(), context = backendContext), + private val fullJs: Boolean = true, + private val dceJs: Boolean = false, + private val multiModule: Boolean = false, + private val relativeRequirePath: Boolean = false, + private val moduleToName: Map = emptyMap(), + private val removeUnusedAssociatedObjects: Boolean = true, +) { + private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS) + + private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! + private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! + + fun generateModule(modules: Iterable): CompilerResult { + val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) + + val exportData = modules.associate { module -> + module to module.files.associate { file -> + file to exportModelGenerator.generateExport(file) + } + } + + val dts = wrapTypeScript(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatMap { it } }.toTypeScript(moduleKind)) + + modules.forEach { module -> + module.files.forEach { StaticMembersLowering(backendContext).lower(it) } + } + + val jsCode = if (fullJs) generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) else null + + val dceJsCode = if (dceJs) { + eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects) + + generateWrappedModuleBody(mainModuleName, moduleKind, generateProgramFragments(modules, exportData)) + } else null + + return CompilerResult(jsCode, dceJsCode, dts) + } + + fun generateBinaryAst(files: Iterable): Map { + val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) + + val exportData = files.associate { file -> + file to exportModelGenerator.generateExport(file) + } + + files.forEach { StaticMembersLowering(backendContext).lower(it) } + + val serializer = JsIrAstSerializer() { "TODO" } + + val result = mutableMapOf() + files.forEach { f -> + val exports = exportData[f]!! // TODO + val fragment = generateProgramFragment(f, exports) + val output = ByteArrayOutputStream() + serializer.serialize(fragment, output) + val binaryAst = output.toByteArray() + result[f.fileEntry.name] = binaryAst + } + + return result + } + + private fun generateProgramFragments( + modules: Iterable, + exportData: Map>>, + ): List> { + + val fragments = mutableMapOf() + modules.forEach { m -> + m.files.forEach { f -> + val exports = exportData[m]!![f]!! // TODO + fragments[f] = 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 pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP) + + private fun generateProgramFragment(file: IrFile, exports: List): JsIrProgramFragment { + val nameGenerator = JsNameLinkingNamer(backendContext) + + val staticContext = JsStaticContext( + backendContext = backendContext, + irNamer = nameGenerator, + globalNameScope = namer.globalNames + ) + + val result = JsIrProgramFragment(file.fqName.asString()) + + val internalModuleName = JsName("_", false) + val globalNames = NameTable(namer.globalNames) + val exportStatements = + ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport( + ExportedModule(mainModuleName, moduleKind, exports), + internalModuleName, + ) + + result.exports.statements += exportStatements + + if (exports.isNotEmpty()) { + result.dts = exports.toTypeScript(moduleKind) + } + + val statements = result.declarations.statements + + val fileStatements = file.accept(IrFileToJsTransformer(), staticContext).statements + if (fileStatements.isNotEmpty()) { + var startComment = "" + + if (generateRegionComments) { + startComment = "region " + } + + if (generateRegionComments || generateFilePaths) { + val originalPath = file.path + val path = pathPrefixMap.entries + .find { (k, _) -> originalPath.startsWith(k) } + ?.let { (k, v) -> v + originalPath.substring(k.length) } + ?: originalPath + + startComment += "file: $path" + } + + if (startComment.isNotEmpty()) { + statements.add(JsSingleLineComment(startComment)) + } + + statements.addAll(fileStatements) + if (generateRegionComments) { + statements += JsSingleLineComment("endregion") + } + } + + staticContext.classModels.entries.forEach { (symbol, model) -> + result.classes[nameGenerator.getNameForClass(symbol.owner)] = + JsIrIcClassModel(model.klass.superTypes.map { staticContext.getNameForClass((it.classifierOrFail as IrClassSymbol).owner) }) + } + + result.initializers.statements += staticContext.initializerBlock.statements + + if (mainArguments != null) { + JsMainFunctionDetector(backendContext).getMainFunctionOrNull(file)?.let { + val jsName = staticContext.getNameForStaticFunction(it) + val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false + val generateContinuation = it.isLoweredSuspendFunction(backendContext) + result.mainFunction = JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, staticContext)).makeStmt() + } + } + + backendContext.testFunsPerFile[file]?.let { + result.testFunInvocation = JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt() + result.suiteFn = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner) + } + + result.importedModules += nameGenerator.importedModules + + fun computeTag(declaration: IrDeclaration): String { + // TODO proper tags + return System.identityHashCode(declaration).toString() + } + + nameGenerator.nameMap.entries.forEach { (declaration, name) -> + val tag = computeTag(declaration) + result.nameBindings[tag] = name + } + + nameGenerator.imports.entries.forEach { (declaration, importExpression) -> + val tag = computeTag(declaration) + result.imports[tag] = importExpression + } + + return result + } + + private fun generateMainArguments( + generateArgv: Boolean, + generateContinuation: Boolean, + staticContext: JsStaticContext, + ): List { + val mainArguments = this.mainArguments!! + val mainArgumentsArray = + if (generateArgv) JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) else null + + val continuation = if (generateContinuation) { + backendContext.coroutineEmptyContinuation.owner + .let { it.getter!! } + .let { staticContext.getNameForStaticFunction(it) } + .let { JsInvocation(it.makeRef()) } + } else null + + return listOfNotNull(mainArgumentsArray, continuation) + } +} + +fun generateWrappedModuleBody( + moduleName: String, + moduleKind: ModuleKind, + fragments: List> +): CompilationOutputs { + val program = Merger( + moduleName, + moduleKind, + fragments, + false, + true + ).merge() + + program.resolveTemporaryNames() + + val jsCode = TextOutputImpl() + + program.accept(JsToStringGenerationVisitor(jsCode, NoOpSourceLocationConsumer)) + + return CompilationOutputs( + jsCode.toString(), + null + ) +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index bcd0a5a5dbb..aa4a3591e20 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -32,7 +32,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } private val classPrototypeRef = prototypeOf(classNameRef) private val classBlock = JsGlobalBlock() - private val classModel = JsIrClassModel(irClass.superTypes.map { context.getNameForClass((it.classifierOrFail as IrClassSymbol).owner) }) + private val classModel = JsIrClassModel(irClass) private val es6mode = context.staticContext.backendContext.es6mode @@ -364,7 +364,14 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo private val IrClassifierSymbol.isInterface get() = (owner as? IrClass)?.isInterface == true private val IrClassifierSymbol.isEffectivelyExternal get() = (owner as? IrDeclaration)?.isEffectivelyExternal() == true -class JsIrClassModel(val superClasses: List) { +class JsIrClassModel(val klass: IrClass) { + val superClasses = klass.superTypes.map { it.classifierOrFail as IrClassSymbol } + val preDeclarationBlock = JsGlobalBlock() val postDeclarationBlock = JsGlobalBlock() } + +class JsIrIcClassModel(val superClasses: List) { + val preDeclarationBlock = JsGlobalBlock() + val postDeclarationBlock = JsGlobalBlock() +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index 60e037d53d3..ad274181b2c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -19,7 +19,7 @@ class JsIrProgramFragment(val packageFqn: String) { val importedModules = mutableListOf() val imports = mutableMapOf() var dts: String? = null - val classes = mutableMapOf() + val classes = mutableMapOf() val initializers = JsGlobalBlock() var mainFunction: JsStatement? = null var testFunInvocation: JsStatement? = null @@ -50,10 +50,10 @@ class Merger( importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression)))) } - val classModels = mutableMapOf() + f.classes + val classModels = mutableMapOf() + f.classes f.classes.clear() classModels.entries.forEach { (name, model) -> - f.classes[rename(name)] = JsIrClassModel(model.superClasses.map { rename(it) }).also { + 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) @@ -115,7 +115,7 @@ class Merger( moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock) - val classModels = mutableMapOf() + val classModels = mutableMapOf() val initializerBlock = JsGlobalBlock() fragments.forEach { it.forEach { @@ -197,7 +197,7 @@ class Merger( private fun processClassModels( - classModelMap: Map, + classModelMap: Map, preDeclarationBlock: JsBlock, postDeclarationBlock: JsBlock ) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt index 61d2f8841ef..9ce584ae3d3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils.serialization import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.js.backend.ast.* @@ -83,8 +84,8 @@ class JsIrAstDeserializer : JsAstDeserializerBase() { return fragment } - private fun deserialize(proto: IrClassModel): Pair { - return deserializeName(proto.nameId) to JsIrClassModel(proto.superClassesList.map { deserializeName(it) }).apply { + private fun deserialize(proto: IrClassModel): Pair { + return deserializeName(proto.nameId) to JsIrIcClassModel(proto.superClassesList.map { deserializeName(it) }).apply { if (proto.hasPreDeclarationBlock()) { preDeclarationBlock.statements += deserializeGlobalBlock(proto.preDeclarationBlock).statements } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt index 8b83105225f..c5dca182821 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils.serialization import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.* @@ -88,7 +89,7 @@ class JsIrAstSerializer: JsAstSerializerBase() { return fragmentBuilder.build() } - private fun serialize(name: JsName, classModel: JsIrClassModel): IrClassModel { + private fun serialize(name: JsName, classModel: JsIrIcClassModel): IrClassModel { val builder = IrClassModel.newBuilder() builder.nameId = serialize(name) classModel.superClasses.forEach { builder.addSuperClasses(serialize(it)) }