[JS IR] Memory consumption optimization

- Remove IR after JS AST generation
 - Remove JS AST after/during JS code generation
This commit is contained in:
Alexander Korepanov
2022-08-15 14:35:37 +02:00
committed by Space
parent 14b7db0187
commit 453faeaa45
8 changed files with 218 additions and 167 deletions
@@ -43,10 +43,7 @@ import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.ic.*
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.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
@@ -55,7 +52,6 @@ import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.KotlinPaths import org.jetbrains.kotlin.utils.KotlinPaths
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
@@ -79,6 +75,78 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
return K2JSCompilerArguments() return K2JSCompilerArguments()
} }
private data class TransformResult(val out: CompilationOutputs, val dts: String)
private class Ir2JsTransformer(
val arguments: K2JSCompilerArguments,
val module: ModulesStructure,
val phaseConfig: PhaseConfig,
val messageCollector: MessageCollector,
val mainCallArguments: List<String>?
) {
private fun lowerIr(): LoweredIr {
val granularity = when {
arguments.irPerModule -> JsGenerationGranularity.PER_MODULE
arguments.irPerFile -> JsGenerationGranularity.PER_FILE
else -> JsGenerationGranularity.WHOLE_PROGRAM
}
val irFactory = when {
arguments.irNewIr2Js -> IrFactoryImplForJsIC(WholeWorldStageController())
else -> IrFactoryImpl
}
return compile(
module,
phaseConfig,
irFactory,
dceRuntimeDiagnostic = RuntimeDiagnostic.resolve(
arguments.irDceRuntimeDiagnostic,
messageCollector
),
baseClassIntoMetadata = arguments.irBaseClassInMetadata,
safeExternalBoolean = arguments.irSafeExternalBoolean,
safeExternalBooleanDiagnostic = RuntimeDiagnostic.resolve(
arguments.irSafeExternalBooleanDiagnostic,
messageCollector
),
granularity = granularity,
icCompatibleIr2Js = arguments.irNewIr2Js,
)
}
private fun makeJsCodeGeneratorAndDts(): Pair<JsCodeGenerator, String> {
val ir = lowerIr()
val transformer = IrModuleToJsTransformerTmp(ir.context, mainCallArguments, ir.moduleFragmentToUniqueName)
val mode = TranslationMode.fromFlags(arguments.irDce, arguments.irPerModule, arguments.irMinimizedMemberNames)
return transformer.makeJsCodeGeneratorAndDts(ir.allModules, mode)
}
fun compileAndTransformIrNew(): TransformResult {
val (generator, dts) = makeJsCodeGeneratorAndDts()
val out = generator.generateJsCode(relativeRequirePath = true, outJsProgram = false)
return TransformResult(out, dts)
}
fun compileAndTransformIrOld(): TransformResult {
val ir = lowerIr()
val transformer = IrModuleToJsTransformer(
ir.context,
mainCallArguments,
fullJs = !arguments.irDce,
dceJs = arguments.irDce,
multiModule = arguments.irPerModule,
relativeRequirePath = true,
moduleToName = ir.moduleFragmentToUniqueName
)
val result = transformer.generateModule(ir.allModules)
return TransformResult(result.outputs.values.single(), result.tsDefinitions ?: error("No ts definitions"))
}
}
override fun doExecute( override fun doExecute(
arguments: K2JSCompilerArguments, arguments: K2JSCompilerArguments,
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
@@ -301,6 +369,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val outputs = jsExecutableProducer.buildExecutable( val outputs = jsExecutableProducer.buildExecutable(
multiModule = arguments.irPerModule, multiModule = arguments.irPerModule,
outJsProgram = false,
rebuildCallback = { rebuiltModule -> rebuildCallback = { rebuiltModule ->
messageCollector.report(INFO, "IC module builder rebuilt module [${File(rebuiltModule).name}]") messageCollector.report(INFO, "IC module builder rebuilt module [${File(rebuiltModule).name}]")
} }
@@ -368,72 +437,23 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val granularity = when {
arguments.irPerModule -> JsGenerationGranularity.PER_MODULE
arguments.irPerFile -> JsGenerationGranularity.PER_FILE
else -> JsGenerationGranularity.WHOLE_PROGRAM
}
try { try {
val irFactory = when {
arguments.irNewIr2Js -> IrFactoryImplForJsIC(WholeWorldStageController())
else -> IrFactoryImpl
}
val ir = compile( val (outputs, tsDefinitions) = if (arguments.irNewIr2Js) {
module, Ir2JsTransformer(arguments, module, phaseConfig, messageCollector, mainCallArguments).compileAndTransformIrNew()
phaseConfig,
irFactory,
dceRuntimeDiagnostic = RuntimeDiagnostic.resolve(
arguments.irDceRuntimeDiagnostic,
messageCollector
),
baseClassIntoMetadata = arguments.irBaseClassInMetadata,
safeExternalBoolean = arguments.irSafeExternalBoolean,
safeExternalBooleanDiagnostic = RuntimeDiagnostic.resolve(
arguments.irSafeExternalBooleanDiagnostic,
messageCollector
),
granularity = granularity,
icCompatibleIr2Js = arguments.irNewIr2Js,
)
val compiledModule: CompilerResult = if (arguments.irNewIr2Js) {
val transformer = IrModuleToJsTransformerTmp(
ir.context,
mainCallArguments,
relativeRequirePath = true,
moduleToName = ir.moduleFragmentToUniqueName
)
transformer.generateModule(
ir.allModules,
setOf(TranslationMode.fromFlags(arguments.irDce, arguments.irPerModule, arguments.irMinimizedMemberNames))
)
} else { } else {
val transformer = IrModuleToJsTransformer( Ir2JsTransformer(arguments, module, phaseConfig, messageCollector, mainCallArguments).compileAndTransformIrOld()
ir.context,
mainCallArguments,
fullJs = !arguments.irDce,
dceJs = arguments.irDce,
multiModule = arguments.irPerModule,
relativeRequirePath = true,
moduleToName = ir.moduleFragmentToUniqueName
)
transformer.generateModule(ir.allModules)
} }
messageCollector.report(INFO, "Executable production duration: ${System.currentTimeMillis() - start}ms") messageCollector.report(INFO, "Executable production duration: ${System.currentTimeMillis() - start}ms")
val outputs = compiledModule.outputs.values.single()
outputFile.write(outputs) outputFile.write(outputs)
outputs.dependencies.forEach { (name, content) -> outputs.dependencies.forEach { (name, content) ->
outputFile.resolveSibling("$name.js").write(content) outputFile.resolveSibling("$name.js").write(content)
} }
if (arguments.generateDts) { if (arguments.generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!! val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!!
dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions")) dtsFile.writeText(tsDefinitions)
} }
} catch (e: CompilationException) { } catch (e: CompilationException) {
messageCollector.report( messageCollector.report(
@@ -38,7 +38,6 @@ fun compileWithIC(
dceRuntimeDiagnostic: RuntimeDiagnostic? = null, dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
es6mode: Boolean = false, es6mode: Boolean = false,
multiModule: Boolean = false, multiModule: Boolean = false,
relativeRequirePath: Boolean = false,
verifySignatures: Boolean = true, verifySignatures: Boolean = true,
baseClassIntoMetadata: Boolean = false, baseClassIntoMetadata: Boolean = false,
lowerPerModule: Boolean = false, lowerPerModule: Boolean = false,
@@ -79,12 +78,7 @@ fun compileWithIC(
lowerPreservingTags(allModules, context, PhaseConfig(jsPhases), symbolTable.irFactory.stageController as WholeWorldStageController) lowerPreservingTags(allModules, context, PhaseConfig(jsPhases), symbolTable.irFactory.stageController as WholeWorldStageController)
val transformer = IrModuleToJsTransformerTmp( val transformer = IrModuleToJsTransformerTmp(context, mainArguments)
context,
mainArguments,
relativeRequirePath = relativeRequirePath,
)
return transformer.generateBinaryAst(filesToLower, allModules) return transformer.generateBinaryAst(filesToLower, allModules)
} }
@@ -16,27 +16,28 @@ class JsExecutableProducer(
private val caches: List<ModuleArtifact>, private val caches: List<ModuleArtifact>,
private val relativeRequirePath: Boolean private val relativeRequirePath: Boolean
) { ) {
fun buildExecutable(multiModule: Boolean, rebuildCallback: (String) -> Unit = {}) = if (multiModule) { fun buildExecutable(multiModule: Boolean, outJsProgram: Boolean, rebuildCallback: (String) -> Unit = {}) = if (multiModule) {
buildMultiModuleExecutable(rebuildCallback) buildMultiModuleExecutable(outJsProgram, rebuildCallback)
} else { } else {
buildSingleModuleExecutable(rebuildCallback) buildSingleModuleExecutable(outJsProgram, rebuildCallback)
} }
private fun buildSingleModuleExecutable(rebuildCallback: (String) -> Unit): CompilationOutputs { private fun buildSingleModuleExecutable(outJsProgram: Boolean, rebuildCallback: (String) -> Unit): CompilationOutputs {
val program = JsIrProgram(caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() }) val modules = caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() }
val out = generateSingleWrappedModuleBody( val out = generateSingleWrappedModuleBody(
moduleName = mainModuleName, moduleName = mainModuleName,
moduleKind = moduleKind, moduleKind = moduleKind,
fragments = program.modules.flatMap { it.fragments }, fragments = modules.flatMap { it.fragments },
sourceMapsInfo = sourceMapsInfo, sourceMapsInfo = sourceMapsInfo,
generateScriptModule = false, generateScriptModule = false,
generateCallToMain = true generateCallToMain = true,
outJsProgram = outJsProgram
) )
rebuildCallback(mainModuleName) rebuildCallback(mainModuleName)
return out return out
} }
private fun buildMultiModuleExecutable(rebuildCallback: (String) -> Unit): CompilationOutputs { private fun buildMultiModuleExecutable(outJsProgram: Boolean, rebuildCallback: (String) -> Unit): CompilationOutputs {
val jsMultiModuleCache = JsMultiModuleCache(caches) val jsMultiModuleCache = JsMultiModuleCache(caches)
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache() val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
@@ -64,7 +65,8 @@ class JsExecutableProducer(
sourceMapsInfo = sourceMapsInfo, sourceMapsInfo = sourceMapsInfo,
generateScriptModule = false, generateScriptModule = false,
generateCallToMain = generateCallToMain, generateCallToMain = generateCallToMain,
crossModuleReferences = crossRef crossModuleReferences = crossRef,
outJsProgram = outJsProgram
) )
jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule) jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule)
rebuildCallback(moduleName) rebuildCallback(moduleName)
@@ -64,11 +64,30 @@ enum class TranslationMode(
class JsIrFragmentAndBinaryAst(val irFile: IrFile, val fragment: JsIrProgramFragment, val binaryAst: ByteArray) class JsIrFragmentAndBinaryAst(val irFile: IrFile, val fragment: JsIrProgramFragment, val binaryAst: ByteArray)
class JsCodeGenerator(
private val program: JsIrProgram,
private val multiModule: Boolean,
private val mainModuleName: String,
private val moduleKind: ModuleKind,
private val sourceMapsInfo: SourceMapsInfo?
) {
fun generateJsCode(relativeRequirePath: Boolean, outJsProgram: Boolean): CompilationOutputs {
return generateWrappedModuleBody(
multiModule,
mainModuleName,
moduleKind,
program,
sourceMapsInfo,
relativeRequirePath,
false,
outJsProgram
)
}
}
class IrModuleToJsTransformerTmp( class IrModuleToJsTransformerTmp(
private val backendContext: JsIrBackendContext, private val backendContext: JsIrBackendContext,
private val mainArguments: List<String>?, private val mainArguments: List<String>?,
private val generateScriptModule: Boolean = false,
private val relativeRequirePath: Boolean = false,
private val moduleToName: Map<IrModuleFragment, String> = emptyMap(), private val moduleToName: Map<IrModuleFragment, String> = emptyMap(),
private val removeUnusedAssociatedObjects: Boolean = true, private val removeUnusedAssociatedObjects: Boolean = true,
) { ) {
@@ -76,40 +95,40 @@ class IrModuleToJsTransformerTmp(
private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration)
fun generateModule(modules: Iterable<IrModuleFragment>, modes: Set<TranslationMode>): CompilerResult { private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List<Pair<IrFile, List<ExportedDeclaration>>>)
private fun List<IrAndExportedDeclarations>.flatExportedDeclarations(): List<ExportedDeclaration> {
return this.flatMap { data -> data.files.flatMap { it.second } }
}
private fun associateIrAndExport(modules: Iterable<IrModuleFragment>): List<IrAndExportedDeclarations> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true)
val exportData = modules.associate { module -> return modules.map { module ->
module to module.files.associate { file -> val files = module.files.map { file ->
file to exportModelGenerator.generateExportWithExternals(file) file to exportModelGenerator.generateExportWithExternals(file)
} }
IrAndExportedDeclarations(module, files)
} }
}
val dts = ExportedModule(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatten() }).toTypeScript() private fun doStaticMembersLowering(modules: Iterable<IrModuleFragment>) {
modules.forEach { module -> modules.forEach { module ->
module.files.forEach { StaticMembersLowering(backendContext).lower(it) } module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
} }
}
fun compilationOutput(multiModule: Boolean, minimizedMemberNames: Boolean) = generateWrappedModuleBody( fun generateModule(modules: Iterable<IrModuleFragment>, modes: Set<TranslationMode>, relativeRequirePath: Boolean): CompilerResult {
multiModule, val exportData = associateIrAndExport(modules)
mainModuleName, val dts = ExportedModule(mainModuleName, moduleKind, exportData.flatExportedDeclarations()).toTypeScript()
moduleKind, doStaticMembersLowering(modules)
generateProgramFragments(modules, exportData, minimizedMemberNames),
SourceMapsInfo.from(backendContext.configuration),
relativeRequirePath,
generateScriptModule,
)
val result = EnumMap<TranslationMode, CompilationOutputs>(TranslationMode::class.java) val result = EnumMap<TranslationMode, CompilationOutputs>(TranslationMode::class.java)
modes.filter { !it.dce }.forEach { modes.filter { !it.dce }.forEach {
if (it.minimizedMemberNames) { result[it] = makeJsCodeGeneratorFromIr(exportData, it).generateJsCode(relativeRequirePath, true)
backendContext.fieldDataCache.clear()
backendContext.minimizedNameGenerator.clear()
}
result[it] = compilationOutput(it.perModule, it.minimizedMemberNames)
} }
if (modes.any { it.dce }) { if (modes.any { it.dce }) {
@@ -117,26 +136,30 @@ class IrModuleToJsTransformerTmp(
} }
modes.filter { it.dce }.forEach { modes.filter { it.dce }.forEach {
if (it.minimizedMemberNames) { result[it] = makeJsCodeGeneratorFromIr(exportData, it).generateJsCode(relativeRequirePath, true)
backendContext.fieldDataCache.clear()
backendContext.minimizedNameGenerator.clear()
}
result[it] = compilationOutput(it.perModule, it.minimizedMemberNames)
} }
return CompilerResult(result, dts) return CompilerResult(result, dts)
} }
fun makeJsCodeGeneratorAndDts(modules: Iterable<IrModuleFragment>, mode: TranslationMode): Pair<JsCodeGenerator, String> {
val exportData = associateIrAndExport(modules)
val dts = ExportedModule(mainModuleName, moduleKind, exportData.flatExportedDeclarations()).toTypeScript()
doStaticMembersLowering(modules)
if (mode.dce) {
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
}
return makeJsCodeGeneratorFromIr(exportData, mode) to dts
}
fun generateBinaryAst(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<JsIrFragmentAndBinaryAst> { fun generateBinaryAst(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<JsIrFragmentAndBinaryAst> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true)
val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) } val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) }
allModules.forEach { doStaticMembersLowering(allModules)
it.files.forEach {
StaticMembersLowering(backendContext).lower(it)
}
}
val serializer = JsIrAstSerializer() val serializer = JsIrAstSerializer()
return exportData.map { (file, exports) -> return exportData.map { (file, exports) ->
@@ -158,29 +181,35 @@ class IrModuleToJsTransformerTmp(
return moduleToName[this] ?: sanitizeName(safeName) return moduleToName[this] ?: sanitizeName(safeName)
} }
private fun generateProgramFragments( private fun makeJsCodeGeneratorFromIr(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsCodeGenerator {
modules: Iterable<IrModuleFragment>, if (mode.minimizedMemberNames) {
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>, backendContext.fieldDataCache.clear()
minimizedMemberNames: Boolean backendContext.minimizedNameGenerator.clear()
): JsIrProgram { }
return JsIrProgram(
modules.map { m -> val program = JsIrProgram(
exportData.map { data ->
JsIrModule( JsIrModule(
m.safeName, data.fragment.safeName,
m.externalModuleName(), data.fragment.externalModuleName(),
m.files.map { data.files.map { (file, exports) ->
val exports = exportData[m]!![it]!! generateProgramFragment(file, exports, mode.minimizedMemberNames)
generateProgramFragment(it, exports, minimizedMemberNames) }
},
) )
} }
) )
return JsCodeGenerator(program, mode.perModule, mainModuleName, moduleKind, sourceMapInfo)
} }
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH) private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP) private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
private fun generateProgramFragment(file: IrFile, exports: List<ExportedDeclaration>, minimizedMemberNames: Boolean): JsIrProgramFragment { private fun generateProgramFragment(
file: IrFile,
exports: List<ExportedDeclaration>,
minimizedMemberNames: Boolean
): JsIrProgramFragment {
val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames) val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames)
val globalNameScope = NameTable<IrDeclaration>() val globalNameScope = NameTable<IrDeclaration>()
@@ -332,48 +361,56 @@ private fun generateWrappedModuleBody(
program: JsIrProgram, program: JsIrProgram,
sourceMapsInfo: SourceMapsInfo?, sourceMapsInfo: SourceMapsInfo?,
relativeRequirePath: Boolean, relativeRequirePath: Boolean,
generateScriptModule: Boolean generateScriptModule: Boolean,
outJsProgram: Boolean
): CompilationOutputs { ): CompilationOutputs {
if (multiModule) { if (multiModule) {
// mutable container allows explicitly remove elements from itself,
val moduleToRef = program.crossModuleDependencies(relativeRequirePath) // so we are able to help GC to free heavy JsIrModule objects
// TODO: It makes sense to invent something better, because this logic can be easily broken
val main = program.mainModule val moduleToRef = program.asCrossModuleDependencies(relativeRequirePath).toMutableList()
val others = program.otherModules val mainModule = moduleToRef.removeLast().let { (main, mainRef) ->
generateSingleWrappedModuleBody(
val mainModule = generateSingleWrappedModuleBody( mainModuleName,
mainModuleName,
moduleKind,
main.fragments,
sourceMapsInfo,
generateScriptModule,
generateCallToMain = true,
moduleToRef[main]!!,
)
val dependencies = others.map { module ->
val moduleName = module.externalModuleName
moduleName to generateSingleWrappedModuleBody(
moduleName,
moduleKind, moduleKind,
module.fragments, main.fragments,
sourceMapsInfo, sourceMapsInfo,
generateScriptModule, generateScriptModule,
generateCallToMain = false, generateCallToMain = true,
moduleToRef[module]!!, mainRef,
outJsProgram
) )
} }
val dependencies = buildList(moduleToRef.size) {
while (moduleToRef.isNotEmpty()) {
moduleToRef.removeFirst().let { (module, moduleRef) ->
val moduleName = module.externalModuleName
val moduleCompilationOutput = generateSingleWrappedModuleBody(
moduleName,
moduleKind,
module.fragments,
sourceMapsInfo,
generateScriptModule,
generateCallToMain = false,
moduleRef,
outJsProgram
)
add(moduleName to moduleCompilationOutput)
}
}
}
return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies) return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies)
} else { } else {
return generateSingleWrappedModuleBody( return generateSingleWrappedModuleBody(
mainModuleName, mainModuleName,
moduleKind, moduleKind,
program.modules.flatMap { it.fragments }, program.asFragments(),
sourceMapsInfo, sourceMapsInfo,
generateScriptModule, generateScriptModule,
generateCallToMain = true, generateCallToMain = true,
outJsProgram = outJsProgram
) )
} }
} }
@@ -386,6 +423,7 @@ fun generateSingleWrappedModuleBody(
generateScriptModule: Boolean, generateScriptModule: Boolean,
generateCallToMain: Boolean, generateCallToMain: Boolean,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty, crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty,
outJsProgram: Boolean = true
): CompilationOutputs { ): CompilationOutputs {
val program = Merger( val program = Merger(
moduleName, moduleName,
@@ -428,7 +466,7 @@ fun generateSingleWrappedModuleBody(
return CompilationOutputs( return CompilationOutputs(
jsCode.toString(), jsCode.toString(),
program, program.takeIf { outJsProgram },
sourceMapBuilder?.build() sourceMapBuilder?.build()
) )
} }
@@ -55,19 +55,23 @@ class JsIrModuleHeader(
val externalNames: Set<String> by lazy { nameBindings.keys - definitions } val externalNames: Set<String> by lazy { nameBindings.keys - definitions }
} }
class JsIrProgram(val modules: List<JsIrModule>) { class JsIrProgram(private var modules: List<JsIrModule>) {
val mainModule = modules.last() fun asCrossModuleDependencies(relativeRequirePath: Boolean): List<Pair<JsIrModule, CrossModuleReferences>> {
val otherModules = modules.dropLast(1)
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() }) val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() })
modules = emptyList()
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
return crossModuleReferences.entries.associate { return crossModuleReferences.entries.map {
val module = it.key.associatedModule ?: error("Internal error: module ${it.key.moduleName} is not loaded") val module = it.key.associatedModule ?: error("Internal error: module ${it.key.moduleName} is not loaded")
it.value.initJsImportsForModule(module) it.value.initJsImportsForModule(module)
module to it.value module to it.value
} }
} }
fun asFragments(): List<JsIrProgramFragment> {
val fragments = modules.flatMap { it.fragments }
modules = emptyList()
return fragments
}
} }
class CrossModuleDependenciesResolver(private val headers: List<JsIrModuleHeader>) { class CrossModuleDependenciesResolver(private val headers: List<JsIrModuleHeader>) {
@@ -258,7 +258,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
) )
val rebuiltModules = mutableSetOf<String>() val rebuiltModules = mutableSetOf<String>()
val jsOutput = jsExecutableProducer.buildExecutable(true) { rebuiltModules += it } val jsOutput = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true) { rebuiltModules += it }
verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS) verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS)
verifyJsCode(projStep.id, testInfo.last().moduleName, jsOutput) verifyJsCode(projStep.id, testInfo.last().moduleName, jsOutput)
} }
@@ -70,13 +70,9 @@ abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
icCompatibleIr2Js = true icCompatibleIr2Js = true
) )
val transformer = IrModuleToJsTransformerTmp( val transformer = IrModuleToJsTransformerTmp(ir.context, emptyList())
ir.context,
emptyList(),
relativeRequirePath = false
)
val compiledResult = transformer.generateModule(ir.allModules, setOf(TranslationMode.FULL_DCE_MINIMIZED_NAMES)) val compiledResult = transformer.generateModule(ir.allModules, setOf(TranslationMode.FULL_DCE_MINIMIZED_NAMES), false)
val dceOutput = compiledResult.outputs[TranslationMode.FULL_DCE_MINIMIZED_NAMES] ?: error("No DCE output") val dceOutput = compiledResult.outputs[TranslationMode.FULL_DCE_MINIMIZED_NAMES] ?: error("No DCE output")
@@ -109,7 +109,7 @@ class JsIrBackendFacade(
caches = testServices.jsIrIncrementalDataProvider.getCaches(), caches = testServices.jsIrIncrementalDataProvider.getCaches(),
relativeRequirePath = false relativeRequirePath = false
) )
jsExecutableProducer.buildExecutable(it.perModule) jsExecutableProducer.buildExecutable(it.perModule, true)
}, },
tsDefinitions = null tsDefinitions = null
) )
@@ -173,11 +173,7 @@ class JsIrBackendFacade(
val dceOutputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, TranslationMode.FULL_DCE_MINIMIZED_NAMES) + ".js") val dceOutputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, TranslationMode.FULL_DCE_MINIMIZED_NAMES) + ".js")
if (!esModules) { if (!esModules) {
if (runNewIr2Js) { if (runNewIr2Js) {
val transformer = IrModuleToJsTransformerTmp( val transformer = IrModuleToJsTransformerTmp(loweredIr.context, mainArguments)
loweredIr.context,
mainArguments,
relativeRequirePath = false
)
// If runIrDce then include DCE results // If runIrDce then include DCE results
// If perModuleOnly then skip whole program // If perModuleOnly then skip whole program
@@ -186,7 +182,8 @@ class JsIrBackendFacade(
.filter { (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) } .filter { (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) }
.filter { it.dce == it.minimizedMemberNames } .filter { it.dce == it.minimizedMemberNames }
.toSet() .toSet()
return BinaryArtifacts.Js.JsIrArtifact(outputFile, transformer.generateModule(loweredIr.allModules, translationModes)).dump(module) val compilationOut = transformer.generateModule(loweredIr.allModules, translationModes, false)
return BinaryArtifacts.Js.JsIrArtifact(outputFile, compilationOut).dump(module)
} else { } else {
val transformer = IrModuleToJsTransformer( val transformer = IrModuleToJsTransformer(
loweredIr.context, loweredIr.context,