From bff433f4e904c1a3ba4b36f9aa02ca8b406c2ed3 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Wed, 6 Sep 2023 09:27:28 +0000 Subject: [PATCH] [K/JS] Support eager initialization for per-file granularity --- .../ir/backend/js/ic/HashCalculatorForIC.kt | 4 ++ .../kotlin/ir/backend/js/ic/JsPerFileCache.kt | 60 +++++++++++++++---- .../ir/backend/js/ic/JsPerModuleCache.kt | 3 + .../irToJs/IrDeclarationToJsTransformer.kt | 8 ++- .../irToJs/IrModuleToJsTransformer.kt | 28 ++++++--- .../irToJs/JsIrProgramFragment.kt | 33 +++++++++- .../backend/js/transformers/irToJs/Merger.kt | 9 ++- .../irToJs/resolveTemporaryNames.kt | 1 + .../ir/backend/js/utils/JsStaticContext.kt | 1 + .../js/utils/serialization/Constants.kt | 1 + .../serialization/JsIrAstDeserializer.kt | 2 + .../utils/serialization/JsIrAstSerializer.kt | 4 ++ .../backend/JsToStringGenerationVisitor.java | 5 +- .../kotlin/js/backend/ast/JsImport.kt | 2 + .../js/test/fir/FirJsBoxTestGenerated.java | 18 ++++++ .../js/test/fir/FirJsES6BoxTestGenerated.java | 18 ++++++ .../js/test/ir/IrBoxJsES6TestGenerated.java | 18 ++++++ .../js/test/ir/IrBoxJsTestGenerated.java | 18 ++++++ .../eagerInitializationGlobal1.kt | 28 +++++++++ .../eagerInitializationGlobal2.kt | 23 +++++++ .../eagerInitializationGlobal3.kt | 19 ++++++ .../eagerInitialization/project.info | 14 ++++- 22 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt create mode 100644 js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt create mode 100644 js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt index 4488e6551c6..42093bea2d6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt @@ -221,6 +221,10 @@ internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalcul update(exports[tag]!!) } + updateForEach(importsWithEffect.sortedBy { it.moduleExporter.externalName }) { import -> + update(import.moduleExporter.externalName) + } + updateForEach(imports.keys.sorted()) { tag -> val import = imports[tag]!! update(tag) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt index 857b5e4b064..3a85f75decf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt @@ -78,7 +78,11 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private fun JsIrProgramFragment.getExportFragmentExternalName(moduleArtifact: ModuleArtifact) = moduleFragmentToExternalName.getExternalNameForExporterFile(name, packageFqn, moduleArtifact.moduleExternalName) - private fun JsIrProgramFragment.asIrModuleHeader(moduleName: String, reexportedIn: String? = null): JsIrModuleHeader { + private fun JsIrProgramFragment.asIrModuleHeader( + moduleName: String, + reexportedIn: String? = null, + importWithEffectIn: String? = null, + ): JsIrModuleHeader { return JsIrModuleHeader( moduleName = moduleName, externalModuleName = moduleName, @@ -86,14 +90,25 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult nameBindings = nameBindings.mapValues { v -> v.value.toString() }, optionalCrossModuleImports = optionalCrossModuleImports, reexportedInModuleWithName = reexportedIn, + importedWithEffectInModuleWithName = importWithEffectIn, associatedModule = null ) } private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()) { LoadedJsIrModuleHeaders( - mainFragment.run { asIrModuleHeader(getMainFragmentExternalName(moduleArtifact)) }, - exportFragment?.run { asIrModuleHeader(mainFragment.getExportFragmentExternalName(moduleArtifact), moduleArtifact.moduleExternalName) }, + mainFragment.run { + asIrModuleHeader( + getMainFragmentExternalName(moduleArtifact), + importWithEffectIn = runIf(hasEffect) { moduleArtifact.moduleExternalName } + ) + }, + exportFragment?.run { + asIrModuleHeader( + mainFragment.getExportFragmentExternalName(moduleArtifact), + reexportedIn = moduleArtifact.moduleExternalName + ) + }, ) } @@ -108,6 +123,8 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName } + + val importWithEffectIn = ifTrue { readString() } val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames() it.jsIrHeader = JsIrModuleHeader( @@ -117,6 +134,7 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult nameBindings = nameBindings, optionalCrossModuleImports = optionalCrossModuleImports, reexportedInModuleWithName = reexportedIn, + importedWithEffectInModuleWithName = importWithEffectIn, associatedModule = null, ) } @@ -149,12 +167,13 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult } } - private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo.SerializableCachedFileInfo) { + private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo) { writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName) cachedFileInfo.crossFileReferencesHash.toProtoStream(this) if (cachedFileInfo is CachedFileInfo.ExportFileCachedInfo) { ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag) } + ifNotNull(cachedFileInfo.jsIrHeader.importedWithEffectInModuleWithName) { writeStringNoTag(it) } commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader) } @@ -167,18 +186,16 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult } is CachedFileInfo.ModuleProxyFileCachedInfo -> { moduleHeaderArtifact?.useCodedOutput { - writeStringNoTag(jsIrHeader.externalModuleName) - crossFileReferencesHash.toProtoStream(this) - commitJsIrModuleHeaderNames(jsIrHeader) + commitSingleFileInfo(this@commitFileInfo) } } is CachedFileInfo.ExportFileCachedInfo -> {} } - private fun ModuleArtifact.generateModuleProxyFileCachedInfo(): CachedFileInfo { + private fun ModuleArtifact.generateModuleProxyFileCachedInfo(importedWithEffectInModuleWithName: String? = null): CachedFileInfo { return CachedFileInfo.ModuleProxyFileCachedInfo( this, - generateProxyIrModuleWith(moduleExternalName, moduleExternalName).makeModuleHeader() + generateProxyIrModuleWith(moduleExternalName, moduleExternalName, importedWithEffectInModuleWithName).makeModuleHeader() ) } @@ -230,7 +247,11 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult override fun loadJsIrModule(cacheInfo: CachedFileInfo): JsIrModule { if (cacheInfo !is CachedFileInfo.SerializableCachedFileInfo) { - return generateProxyIrModuleWith(cacheInfo.jsIrHeader.externalModuleName, cacheInfo.jsIrHeader.externalModuleName) + return generateProxyIrModuleWith( + cacheInfo.jsIrHeader.externalModuleName, + cacheInfo.jsIrHeader.externalModuleName, + cacheInfo.jsIrHeader.importedWithEffectInModuleWithName + ) } val fragments = cacheInfo.fileArtifact.loadJsIrFragments() @@ -244,8 +265,11 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult } override fun loadProgramHeadersFromCache(): List { + var someModuleHasEffect = false + val mainModuleArtifact = moduleArtifacts.last() return moduleArtifacts .flatMap { moduleArtifact -> + var hasModuleLevelEffect = false var hasFileWithJsExportedDeclaration = false moduleArtifact.fileArtifacts .flatMap { srcFileArtifact -> @@ -257,11 +281,21 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult if (!hasFileWithJsExportedDeclaration && cachedFileInfo.hasExportFile()) { hasFileWithJsExportedDeclaration = true } + if (!hasModuleLevelEffect && cachedFileInfo.hasModuleLevelEffect()) { + someModuleHasEffect = true + hasModuleLevelEffect = true + } cachedFileInfo } - .butIf(hasFileWithJsExportedDeclaration) { - it.plus(moduleArtifact.fetchModuleProxyFileInfo() ?: moduleArtifact.generateModuleProxyFileCachedInfo()) + .butIf(hasFileWithJsExportedDeclaration || hasModuleLevelEffect || (someModuleHasEffect && moduleArtifact === mainModuleArtifact)) { list -> + val importWithEffectIn = mainModuleArtifact.moduleExternalName.takeIf { + hasModuleLevelEffect && moduleArtifact !== mainModuleArtifact + } + val fetchedProxyFileInfo = moduleArtifact.fetchModuleProxyFileInfo()?.takeIf { + it.jsIrHeader.importedWithEffectInModuleWithName == importWithEffectIn + } + list.plus(fetchedProxyFileInfo ?: moduleArtifact.generateModuleProxyFileCachedInfo(importWithEffectIn)) } } .onEach { headerToCachedInfo[it.jsIrHeader] = it } @@ -288,4 +322,6 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private data class LoadedJsIrModuleHeaders(val mainHeader: JsIrModuleHeader, val exportHeader: JsIrModuleHeader?) private fun List.hasExportFile(): Boolean = size > 1 + private fun List.hasModuleLevelEffect(): Boolean = + last().jsIrHeader.importedWithEffectInModuleWithName != null } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerModuleCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerModuleCache.kt index 0464c90f521..ab7484f87ea 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerModuleCache.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerModuleCache.kt @@ -27,6 +27,7 @@ class JsPerModuleCache(private val moduleArtifacts: List) : JsMu private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, JS_MODULE_HEADER).useCodedInputIfExists { val crossModuleReferencesHash = ICHash.fromProtoStream(this) val reexportedInModuleWithName = ifTrue { readString() } + val importedWithEffectInModuleWithName = ifTrue { readString() } val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames() CachedModuleInfo( @@ -38,6 +39,7 @@ class JsPerModuleCache(private val moduleArtifacts: List) : JsMu nameBindings = nameBindings, optionalCrossModuleImports = optionalCrossModuleImports, reexportedInModuleWithName = reexportedInModuleWithName, + importedWithEffectInModuleWithName = importedWithEffectInModuleWithName, associatedModule = null ), crossModuleReferencesHash = crossModuleReferencesHash @@ -48,6 +50,7 @@ class JsPerModuleCache(private val moduleArtifacts: List) : JsMu File(cacheDir, JS_MODULE_HEADER).useCodedOutput { crossModuleReferencesHash.toProtoStream(this) ifNotNull(jsIrHeader.reexportedInModuleWithName) { writeStringNoTag(it) } + ifNotNull(jsIrHeader.importedWithEffectInModuleWithName) { writeStringNoTag(it) } commitJsIrModuleHeaderNames(jsIrHeader) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrDeclarationToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrDeclarationToJsTransformer.kt index a7b231e96f3..15f92281337 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrDeclarationToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrDeclarationToJsTransformer.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.ir.backend.js.lower.JsCodeOutliningLowering import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.js.backend.ast.* @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @@ -41,8 +42,13 @@ class IrDeclarationToJsTransformer : BaseIrElementToJsNodeTransformer context.staticContext.eagerInitializerBlock + else -> context.staticContext.initializerBlock + } + initializerBlock.statements += jsAssignment(fieldName.makeRef(), initializer).makeStmt() } return JsVars(JsVars.JsVar(fieldName)) 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 be728444288..9d28c007cae 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 @@ -47,10 +47,11 @@ val String.safeModuleName: String val IrModuleFragment.safeName: String get() = name.asString().safeModuleName -fun generateProxyIrModuleWith(safeName: String, externalName: String) = JsIrModule( +fun generateProxyIrModuleWith(safeName: String, externalName: String, importedWithEffectInModuleWithName: String? = null) = JsIrModule( safeName, externalName, - listOf(JsIrProgramFragment(safeName, "")) + listOf(JsIrProgramFragment(safeName, "")), + importedWithEffectInModuleWithName = importedWithEffectInModuleWithName ) enum class JsGenerationGranularity { @@ -264,15 +265,25 @@ class IrModuleToJsTransformer( } private fun generateJsIrProgramPerFile(exportData: List, mode: TranslationMode): JsIrProgram { + val mainModule = exportData.last() + var someModuleHasEffect = false + val modulesPerFile = buildList { for (module in exportData) { + var hasModuleLevelEffect = false var hasFileWithJsExportedDeclaration = false for (fileExports in module.files) { if (fileExports.file.couldBeSkipped()) continue val programFragments = generateProgramFragment(fileExports, mode) - add(fileExports.toJsIrModule(programFragments.mainFragment)) + fileExports.toJsIrModule(module, programFragments.mainFragment).let { + add(it) + if (it.importedWithEffectInModuleWithName != null) { + someModuleHasEffect = true + hasModuleLevelEffect = true + } + } programFragments.exportFragment?.let { add(fileExports.toJsIrModuleForExport(module, it)) @@ -280,8 +291,8 @@ class IrModuleToJsTransformer( } } - if (hasFileWithJsExportedDeclaration) { - add(module.toJsIrProxyModule()) + if (hasFileWithJsExportedDeclaration || hasModuleLevelEffect || (module === mainModule && someModuleHasEffect)) { + add(module.toJsIrProxyModule(mainModule.fragment.safeName.takeIf { module !== mainModule && hasModuleLevelEffect })) } } } @@ -289,11 +300,12 @@ class IrModuleToJsTransformer( return JsIrProgram(modulesPerFile) } - private fun IrFileExports.toJsIrModule(programFragment: JsIrProgramFragment): JsIrModule { + private fun IrFileExports.toJsIrModule(module: IrAndExportedDeclarations, programFragment: JsIrProgramFragment): JsIrModule { return JsIrModule( moduleFragmentToNameMapper.getSafeNameFor(file), moduleFragmentToNameMapper.getExternalNameFor(file), listOf(programFragment), + importedWithEffectInModuleWithName = runIf(programFragment.hasEffect) { module.fragment.safeName } ) } @@ -306,10 +318,11 @@ class IrModuleToJsTransformer( ) } - private fun IrAndExportedDeclarations.toJsIrProxyModule(): JsIrModule { + private fun IrAndExportedDeclarations.toJsIrProxyModule(importedWithEffectInModuleWithName: String? = null): JsIrModule { return generateProxyIrModuleWith( fragment.safeName, moduleFragmentToNameMapper.getExternalNameFor(fragment), + importedWithEffectInModuleWithName ) } @@ -391,6 +404,7 @@ class IrModuleToJsTransformer( } result.initializers.statements += staticContext.initializerBlock.statements + result.eagerInitializers.statements += staticContext.eagerInitializerBlock.statements if (mainArguments != null) { JsMainFunctionDetector(backendContext).getMainFunctionOrNull(fileExports.file)?.let { 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 881c5014b15..eca6666a8b4 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 @@ -22,6 +22,7 @@ class JsIrProgramFragment(val name: String, val packageFqn: String) { var dts: TypeScriptFragment? = null val classes = mutableMapOf() val initializers = JsCompositeBlock() + val eagerInitializers = JsCompositeBlock() var mainFunction: JsStatement? = null var testFunInvocation: JsStatement? = null var suiteFn: JsName? = null @@ -34,6 +35,7 @@ class JsIrModule( val externalModuleName: String, val fragments: List, val reexportedInModuleWithName: String? = null, + val importedWithEffectInModuleWithName: String? = null, ) { fun makeModuleHeader(): JsIrModuleHeader { val nameBindings = mutableMapOf() @@ -55,6 +57,7 @@ class JsIrModule( nameBindings = nameBindings, optionalCrossModuleImports = optionalCrossModuleImports, reexportedInModuleWithName = reexportedInModuleWithName.takeIf { hasDeclarationsToReexport }, + importedWithEffectInModuleWithName = importedWithEffectInModuleWithName, associatedModule = this ) } @@ -67,6 +70,7 @@ class JsIrModuleHeader( val nameBindings: Map, val optionalCrossModuleImports: Set, val reexportedInModuleWithName: String? = null, + val importedWithEffectInModuleWithName: String? = null, var associatedModule: JsIrModule?, ) { val externalNames: Set by lazy(LazyThreadSafetyMode.NONE) { nameBindings.keys - definitions } @@ -94,12 +98,14 @@ class JsIrProgram(private var modules: List) { class CrossModuleDependenciesResolver(private val moduleKind: ModuleKind, private val headers: List) { fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map { val reexportModuleToHeader = headers.groupBy { it.reexportedInModuleWithName } + val importedInModuleWithEffect = headers.groupBy { it.importedWithEffectInModuleWithName } val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferenceBuilder( moduleKind, it, relativeRequirePath, reexportModuleToHeader[it.moduleName] ?: emptyList(), + importedInModuleWithEffect[it.moduleName] ?: emptyList(), ) } val definitionModule = mutableMapOf() @@ -143,6 +149,7 @@ private class JsIrModuleCrossModuleReferenceBuilder( val header: JsIrModuleHeader, val relativeRequirePath: Boolean, val transitiveExportFrom: List, + val importWithEffectFrom: List, ) { val imports = mutableListOf() val exports = mutableSetOf() @@ -186,10 +193,14 @@ private class JsIrModuleCrossModuleReferenceBuilder( CrossModuleTransitiveExport(import(it).internalName, relativeRequirePath(it) ?: it.externalModuleName) } } + + val importsWithEffect = importWithEffectFrom.map { CrossModuleImportWithEffect(import(it)) } + return CrossModuleReferences( moduleKind, importedModules.values.toList(), transitiveExport, + importsWithEffect, exportNames, resultImports ) @@ -220,8 +231,9 @@ private class JsIrModuleCrossModuleReferenceBuilder( } } -class CrossModuleImport(val exportedAs: String, val moduleExporter: JsImportedModule) +class CrossModuleImport(val exportedAs: String, val moduleExporter: JsImportedModule) +class CrossModuleImportWithEffect(val moduleExporter: JsImportedModule) class CrossModuleTransitiveExport(val internalName: JsName, val externalName: String) fun CrossModuleTransitiveExport.getRequireEsmName() = "$externalName$ESM_EXTENSION" @@ -230,6 +242,7 @@ class CrossModuleReferences( val moduleKind: ModuleKind, val importedModules: List, // additional Kotlin imported modules val transitiveExportFrom: List, // the list of modules which provide their exports for transitive export + val importsWithEffect: List, // the list of modules which provide their effects for import val exports: Map, // tag -> index val imports: Map, // tag -> import statement ) { @@ -237,6 +250,8 @@ class CrossModuleReferences( var jsImports = emptyMap() // tag -> import statement private set + val jsImportsWithEffect: List = importsWithEffect.map { it.generateCrossModuleImportStatement() } + fun initJsImportsForModule(module: JsIrModule) { val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value } jsImports = imports.entries.associate { @@ -252,6 +267,17 @@ class CrossModuleReferences( } } + private fun CrossModuleImportWithEffect.generateCrossModuleImportStatement(): JsStatement { + return when (moduleKind) { + ModuleKind.ES -> generateJsImportStatement() + else -> error("Should not appear in non ES-modules compilations") + } + } + + private fun CrossModuleImportWithEffect.generateJsImportStatement(): JsStatement { + return JsImport(moduleExporter.getRequireName(true), JsImport.Target.Effect) + } + private fun CrossModuleImport.generateImportVariableDeclaration(importedAs: JsName): JsStatement { val exportRef = JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleExporter.internalName)) return JsVars(JsVars.JsVar(importedAs, exportRef)) @@ -265,7 +291,7 @@ class CrossModuleReferences( } companion object { - fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyMap(), emptyMap()) + fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyList(), emptyMap(), emptyMap()) } } @@ -278,4 +304,5 @@ fun JsStatement.renameImportedSymbolInternalName(newName: JsName): JsStatement { } val List.mainFragment: JsIrProgramFragment get() = first() -val List.exportFragment: JsIrProgramFragment? get() = getOrNull(1) \ No newline at end of file +val List.exportFragment: JsIrProgramFragment? get() = getOrNull(1) +val JsIrProgramFragment.hasEffect: Boolean get() = eagerInitializers.statements.isNotEmpty() \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt index 67bdc2e01d4..41237e619b8 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -22,6 +22,7 @@ class Merger( private val isEsModules = moduleKind == ModuleKind.ES private val importStatements = mutableMapOf() + private val importStatementsWithEffect = mutableListOf() private val importedModulesMap = mutableMapOf() private val additionalExports = mutableListOf() @@ -57,6 +58,7 @@ class Merger( } rename(f.initializers) + rename(f.eagerInitializers) f.mainFunction?.let { rename(it) } f.testFunInvocation?.let { rename(it) } f.suiteFn?.let { f.suiteFn = rename(it) } @@ -68,6 +70,8 @@ class Merger( importStatements.putIfAbsent(tag, crossModuleJsImport.renameImportedSymbolInternalName(importName)) } + importStatementsWithEffect.addAll(crossModuleReferences.jsImportsWithEffect) + if (crossModuleReferences.exports.isNotEmpty()) { val internalModuleName = ReservedJsNames.makeInternalModuleName() @@ -213,7 +217,7 @@ class Merger( fragments.forEach { moduleBody += it.declarations.statements classModels += it.classes - initializerBlock.statements += it.initializers.statements + initializerBlock.statements += it.initializers.statements + it.eagerInitializers.statements polyfillDeclarationBlock.statements += it.polyfills.statements } @@ -248,7 +252,7 @@ class Merger( val exportStatements = declareAndCallJsExporter() + additionalExports + transitiveJsExport() val importedJsModules = this.importedModulesMap.values.toList() + this.crossModuleReferences.importedModules - val importStatements = this.importStatements.values.toList() + val importStatements = this.importStatements.values.toList() + this.importStatementsWithEffect.toList() val program = JsProgram() @@ -342,6 +346,7 @@ class Merger( is JsImport -> JsImport( module, when (target) { + is JsImport.Target.Effect -> JsImport.Target.Effect is JsImport.Target.All -> JsImport.Target.All(alias = name.makeRef()) is JsImport.Target.Default -> JsImport.Target.Default(name = name.makeRef()) is JsImport.Target.Elements -> JsImport.Target.Elements( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt index a6263f56030..85b81011ce5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt @@ -119,6 +119,7 @@ private fun JsNode.computeScopes(): Scope { override fun visitImport(import: JsImport) { when (val target = import.target) { + is JsImport.Target.Effect -> {} is JsImport.Target.All -> target.alias.name?.let { currentScope.declaredNames += it } is JsImport.Target.Default -> target.name.name?.let { currentScope.declaredNames += it } is JsImport.Target.Elements -> target.elements.forEach { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt index bd52fbe754a..0d8dc9c2ebf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt @@ -25,6 +25,7 @@ class JsStaticContext( val classModels = mutableMapOf() val initializerBlock = JsCompositeBlock() + val eagerInitializerBlock = JsCompositeBlock() val isPerFile: Boolean get() = mode.granularity === JsGenerationGranularity.PER_FILE } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/Constants.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/Constants.kt index 870dd57c667..358792f8f6e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/Constants.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/Constants.kt @@ -37,6 +37,7 @@ object ImportType { const val ALL = 0 const val ITEMS = 1 const val DEFAULT = 2 + const val EFFECT = 3 } object ExportType { 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 774fad7f6be..89a9aa034f2 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 @@ -94,6 +94,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) { readRepeated { declarations.statements += readStatement() } readRepeated { initializers.statements += readStatement() } + readRepeated { eagerInitializers.statements += readStatement() } readRepeated { exports.statements += readStatement() } readRepeated { polyfills.statements += readStatement() } @@ -238,6 +239,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) { JsImport( readString(), when (val type = readByte().toInt()) { + ImportType.EFFECT -> JsImport.Target.Effect ImportType.ALL -> JsImport.Target.All(nameTable[readInt()].makeRef()) ImportType.DEFAULT -> JsImport.Target.Default(nameTable[readInt()].makeRef()) ImportType.ITEMS -> JsImport.Target.Elements(readList { 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 ae7aad0d38b..99e6dcf4b2d 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 @@ -132,6 +132,7 @@ private class JsIrAstSerializer { writeCompositeBlock(fragment.declarations) writeCompositeBlock(fragment.initializers) + writeCompositeBlock(fragment.eagerInitializers) writeCompositeBlock(fragment.exports) writeCompositeBlock(fragment.polyfills) @@ -328,6 +329,9 @@ private class JsIrAstSerializer { writeString(import.module) when (val target = import.target) { + is JsImport.Target.Effect -> { + writeByte(ImportType.EFFECT) + } is JsImport.Target.All -> { writeByte(ImportType.ALL) writeInt(internalizeName(target.alias.name!!)) diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java index e828c9a96c6..94b431045e3 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java @@ -1404,7 +1404,10 @@ public class JsToStringGenerationVisitor extends JsVisitor { p.print("}"); } - p.print(" from "); + if (!(target == JsImport.Target.Effect.INSTANCE)) { + p.print(" from "); + } + p.print(javaScriptString(jsImport.getModule())); } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt index 13e23cdc0a4..e8b6f4395bd 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt @@ -15,6 +15,7 @@ class JsImport( get() = (target as Target.Elements).elements sealed class Target { + object Effect : Target() class Elements(val elements: MutableList) : Target() class Default(val name: JsNameRef) : Target() { constructor(name: String) : this(JsNameRef(name)) @@ -36,6 +37,7 @@ class JsImport( override fun acceptChildren(visitor: JsVisitor) { when (target) { + is Target.Effect -> {} is Target.All -> visitor.accept(target.alias) is Target.Default -> visitor.accept(target.name) is Target.Elements -> target.elements.forEach { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 60300d392d6..c97eabd07e1 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -2041,6 +2041,24 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); } + @Test + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal3.kt") + public void testEagerInitializationGlobal3() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt"); + } + @Test @TestMetadata("inheritance.kt") public void testInheritance() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java index a984f0e8a72..c57a68bbfc5 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java @@ -2147,6 +2147,24 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest { runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); } + @Test + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal3.kt") + public void testEagerInitializationGlobal3() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt"); + } + @Test @TestMetadata("inheritance.kt") public void testInheritance() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 389fb1aed7d..c6cf0a79147 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -2147,6 +2147,24 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); } + @Test + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal3.kt") + public void testEagerInitializationGlobal3() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt"); + } + @Test @TestMetadata("inheritance.kt") public void testInheritance() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index e979bf20fac..d4abd030765 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -2041,6 +2041,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); } + @Test + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt"); + } + + @Test + @TestMetadata("eagerInitializationGlobal3.kt") + public void testEagerInitializationGlobal3() throws Exception { + runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt"); + } + @Test @TestMetadata("inheritance.kt") public void testInheritance() throws Exception { diff --git a/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt new file mode 100644 index 00000000000..1464088786f --- /dev/null +++ b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal1.kt @@ -0,0 +1,28 @@ +// TARGET_BACKEND: JS_IR +// IGNORE_BACKEND: WASM +// ES_MODULES +// PROPERTY_LAZY_INITIALIZATION + +// FILE: lib.kt +var z1 = false +var z2 = false + +// FILE: lib2.kt + +@OptIn(kotlin.ExperimentalStdlibApi::class) +@EagerInitialization +val x = foo() + +private fun foo(): Int { + z1 = true + return 42 +} + +// Will be initialized since [x]'s initializer calls a function from the file. +val y = run { z2 = true; 117 } + +// FILE: main.kt + +fun box(): String { + return return if (z1 && z2) "OK" else "fail" +} diff --git a/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt new file mode 100644 index 00000000000..acf1e372eeb --- /dev/null +++ b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal2.kt @@ -0,0 +1,23 @@ +// TARGET_BACKEND: JS_IR +// IGNORE_BACKEND: WASM +// ES_MODULES +// PROPERTY_LAZY_INITIALIZATION + +// FILE: lib.kt +var z1 = false +var z2 = false + +// FILE: lib2.kt + +@OptIn(kotlin.ExperimentalStdlibApi::class) +@EagerInitialization +val x = run { z1 = true; 42 } + +// Won't be initialized (cause no function from the file will be called during [x] initialization). +val y = run { z2 = true; 117 } + +// FILE: main.kt + +fun box(): String { + return if (z1 && !z2) "OK" else "fail" +} diff --git a/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt new file mode 100644 index 00000000000..a72adeda851 --- /dev/null +++ b/js/js.translator/testData/box/esModules/crossModuleRefPerFile/eagerInitializationGlobal3.kt @@ -0,0 +1,19 @@ +// TARGET_BACKEND: JS_IR +// IGNORE_BACKEND: WASM +// ES_MODULES +// PROPERTY_LAZY_INITIALIZATION + +// FILE: lib.kt +var z1 = false + +// FILE: lib2.kt + +@OptIn(kotlin.ExperimentalStdlibApi::class) +@EagerInitialization +val x = run { z1 = !z1; 42 } + +val y = run { 73 } + +fun box(): String { + return if (z1) "OK" else "fail" +} diff --git a/js/js.translator/testData/incremental/invalidation/eagerInitialization/project.info b/js/js.translator/testData/incremental/invalidation/eagerInitialization/project.info index 8df84dacff5..d2314d28f53 100644 --- a/js/js.translator/testData/incremental/invalidation/eagerInitialization/project.info +++ b/js/js.translator/testData/incremental/invalidation/eagerInitialization/project.info @@ -1,10 +1,18 @@ -IGNORE_PER_FILE: true - MODULES: lib1, main STEP 0: libs: lib1, main dirty js modules: lib1, main -STEP 1..3: + dirty js files: lib1/l1, lib1/l2, main/m, main/m.export, main +STEP 1: libs: lib1, main dirty js modules: lib1 + dirty js files: lib1/l2, lib1, main +STEP 2: + libs: lib1, main + dirty js modules: lib1 + dirty js files: lib1/l2 +STEP 3: + libs: lib1, main + dirty js modules: lib1 + dirty js files: lib1/l2, main