[K/JS] Support eager initialization for per-file granularity

This commit is contained in:
Artem Kobzar
2023-09-06 09:27:28 +00:00
committed by Space Team
parent f203681ffa
commit bff433f4e9
22 changed files with 288 additions and 29 deletions
@@ -221,6 +221,10 @@ internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalcul
update(exports[tag]!!) update(exports[tag]!!)
} }
updateForEach(importsWithEffect.sortedBy { it.moduleExporter.externalName }) { import ->
update(import.moduleExporter.externalName)
}
updateForEach(imports.keys.sorted()) { tag -> updateForEach(imports.keys.sorted()) { tag ->
val import = imports[tag]!! val import = imports[tag]!!
update(tag) update(tag)
@@ -78,7 +78,11 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private fun JsIrProgramFragment.getExportFragmentExternalName(moduleArtifact: ModuleArtifact) = private fun JsIrProgramFragment.getExportFragmentExternalName(moduleArtifact: ModuleArtifact) =
moduleFragmentToExternalName.getExternalNameForExporterFile(name, packageFqn, moduleArtifact.moduleExternalName) 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( return JsIrModuleHeader(
moduleName = moduleName, moduleName = moduleName,
externalModuleName = moduleName, externalModuleName = moduleName,
@@ -86,14 +90,25 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
nameBindings = nameBindings.mapValues { v -> v.value.toString() }, nameBindings = nameBindings.mapValues { v -> v.value.toString() },
optionalCrossModuleImports = optionalCrossModuleImports, optionalCrossModuleImports = optionalCrossModuleImports,
reexportedInModuleWithName = reexportedIn, reexportedInModuleWithName = reexportedIn,
importedWithEffectInModuleWithName = importWithEffectIn,
associatedModule = null associatedModule = null
) )
} }
private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()) { private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()) {
LoadedJsIrModuleHeaders( LoadedJsIrModuleHeaders(
mainFragment.run { asIrModuleHeader(getMainFragmentExternalName(moduleArtifact)) }, mainFragment.run {
exportFragment?.run { asIrModuleHeader(mainFragment.getExportFragmentExternalName(moduleArtifact), moduleArtifact.moduleExternalName) }, 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<ModuleArtifact>) : JsMult
reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName
} }
val importWithEffectIn = ifTrue { readString() }
val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames() val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames()
it.jsIrHeader = JsIrModuleHeader( it.jsIrHeader = JsIrModuleHeader(
@@ -117,6 +134,7 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
nameBindings = nameBindings, nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports, optionalCrossModuleImports = optionalCrossModuleImports,
reexportedInModuleWithName = reexportedIn, reexportedInModuleWithName = reexportedIn,
importedWithEffectInModuleWithName = importWithEffectIn,
associatedModule = null, associatedModule = null,
) )
} }
@@ -149,12 +167,13 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
} }
} }
private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo.SerializableCachedFileInfo) { private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo) {
writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName) writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName)
cachedFileInfo.crossFileReferencesHash.toProtoStream(this) cachedFileInfo.crossFileReferencesHash.toProtoStream(this)
if (cachedFileInfo is CachedFileInfo.ExportFileCachedInfo) { if (cachedFileInfo is CachedFileInfo.ExportFileCachedInfo) {
ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag) ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag)
} }
ifNotNull(cachedFileInfo.jsIrHeader.importedWithEffectInModuleWithName) { writeStringNoTag(it) }
commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader) commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader)
} }
@@ -167,18 +186,16 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
} }
is CachedFileInfo.ModuleProxyFileCachedInfo -> { is CachedFileInfo.ModuleProxyFileCachedInfo -> {
moduleHeaderArtifact?.useCodedOutput { moduleHeaderArtifact?.useCodedOutput {
writeStringNoTag(jsIrHeader.externalModuleName) commitSingleFileInfo(this@commitFileInfo)
crossFileReferencesHash.toProtoStream(this)
commitJsIrModuleHeaderNames(jsIrHeader)
} }
} }
is CachedFileInfo.ExportFileCachedInfo -> {} is CachedFileInfo.ExportFileCachedInfo -> {}
} }
private fun ModuleArtifact.generateModuleProxyFileCachedInfo(): CachedFileInfo { private fun ModuleArtifact.generateModuleProxyFileCachedInfo(importedWithEffectInModuleWithName: String? = null): CachedFileInfo {
return CachedFileInfo.ModuleProxyFileCachedInfo( return CachedFileInfo.ModuleProxyFileCachedInfo(
this, this,
generateProxyIrModuleWith(moduleExternalName, moduleExternalName).makeModuleHeader() generateProxyIrModuleWith(moduleExternalName, moduleExternalName, importedWithEffectInModuleWithName).makeModuleHeader()
) )
} }
@@ -230,7 +247,11 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
override fun loadJsIrModule(cacheInfo: CachedFileInfo): JsIrModule { override fun loadJsIrModule(cacheInfo: CachedFileInfo): JsIrModule {
if (cacheInfo !is CachedFileInfo.SerializableCachedFileInfo) { 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() val fragments = cacheInfo.fileArtifact.loadJsIrFragments()
@@ -244,8 +265,11 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
} }
override fun loadProgramHeadersFromCache(): List<CachedFileInfo> { override fun loadProgramHeadersFromCache(): List<CachedFileInfo> {
var someModuleHasEffect = false
val mainModuleArtifact = moduleArtifacts.last()
return moduleArtifacts return moduleArtifacts
.flatMap { moduleArtifact -> .flatMap { moduleArtifact ->
var hasModuleLevelEffect = false
var hasFileWithJsExportedDeclaration = false var hasFileWithJsExportedDeclaration = false
moduleArtifact.fileArtifacts moduleArtifact.fileArtifacts
.flatMap { srcFileArtifact -> .flatMap { srcFileArtifact ->
@@ -257,11 +281,21 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
if (!hasFileWithJsExportedDeclaration && cachedFileInfo.hasExportFile()) { if (!hasFileWithJsExportedDeclaration && cachedFileInfo.hasExportFile()) {
hasFileWithJsExportedDeclaration = true hasFileWithJsExportedDeclaration = true
} }
if (!hasModuleLevelEffect && cachedFileInfo.hasModuleLevelEffect()) {
someModuleHasEffect = true
hasModuleLevelEffect = true
}
cachedFileInfo cachedFileInfo
} }
.butIf(hasFileWithJsExportedDeclaration) { .butIf(hasFileWithJsExportedDeclaration || hasModuleLevelEffect || (someModuleHasEffect && moduleArtifact === mainModuleArtifact)) { list ->
it.plus(moduleArtifact.fetchModuleProxyFileInfo() ?: moduleArtifact.generateModuleProxyFileCachedInfo()) 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 } .onEach { headerToCachedInfo[it.jsIrHeader] = it }
@@ -288,4 +322,6 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private data class LoadedJsIrModuleHeaders(val mainHeader: JsIrModuleHeader, val exportHeader: JsIrModuleHeader?) private data class LoadedJsIrModuleHeaders(val mainHeader: JsIrModuleHeader, val exportHeader: JsIrModuleHeader?)
private fun List<JsPerFileCache.CachedFileInfo>.hasExportFile(): Boolean = size > 1 private fun List<JsPerFileCache.CachedFileInfo>.hasExportFile(): Boolean = size > 1
private fun List<JsPerFileCache.CachedFileInfo>.hasModuleLevelEffect(): Boolean =
last().jsIrHeader.importedWithEffectInModuleWithName != null
} }
@@ -27,6 +27,7 @@ class JsPerModuleCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMu
private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, JS_MODULE_HEADER).useCodedInputIfExists { private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, JS_MODULE_HEADER).useCodedInputIfExists {
val crossModuleReferencesHash = ICHash.fromProtoStream(this) val crossModuleReferencesHash = ICHash.fromProtoStream(this)
val reexportedInModuleWithName = ifTrue { readString() } val reexportedInModuleWithName = ifTrue { readString() }
val importedWithEffectInModuleWithName = ifTrue { readString() }
val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames() val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames()
CachedModuleInfo( CachedModuleInfo(
@@ -38,6 +39,7 @@ class JsPerModuleCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMu
nameBindings = nameBindings, nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports, optionalCrossModuleImports = optionalCrossModuleImports,
reexportedInModuleWithName = reexportedInModuleWithName, reexportedInModuleWithName = reexportedInModuleWithName,
importedWithEffectInModuleWithName = importedWithEffectInModuleWithName,
associatedModule = null associatedModule = null
), ),
crossModuleReferencesHash = crossModuleReferencesHash crossModuleReferencesHash = crossModuleReferencesHash
@@ -48,6 +50,7 @@ class JsPerModuleCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMu
File(cacheDir, JS_MODULE_HEADER).useCodedOutput { File(cacheDir, JS_MODULE_HEADER).useCodedOutput {
crossModuleReferencesHash.toProtoStream(this) crossModuleReferencesHash.toProtoStream(this)
ifNotNull(jsIrHeader.reexportedInModuleWithName) { writeStringNoTag(it) } ifNotNull(jsIrHeader.reexportedInModuleWithName) { writeStringNoTag(it) }
ifNotNull(jsIrHeader.importedWithEffectInModuleWithName) { writeStringNoTag(it) }
commitJsIrModuleHeaderNames(jsIrHeader) commitJsIrModuleHeaderNames(jsIrHeader)
} }
} }
@@ -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.lower.JsCodeOutliningLowering
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
@@ -41,8 +42,13 @@ class IrDeclarationToJsTransformer : BaseIrElementToJsNodeTransformer<JsStatemen
if (declaration.isExternal) return JsEmpty if (declaration.isExternal) return JsEmpty
if (declaration.initializer != null) { if (declaration.initializer != null) {
val eagerInitializationAnnotation = context.staticContext.backendContext.propertyLazyInitialization.eagerInitialization
val initializer = declaration.initializer!!.accept(IrElementToJsExpressionTransformer(), context) val initializer = declaration.initializer!!.accept(IrElementToJsExpressionTransformer(), context)
context.staticContext.initializerBlock.statements += jsAssignment(fieldName.makeRef(), initializer).makeStmt() val initializerBlock = when {
declaration.correspondingPropertySymbol?.owner?.hasAnnotation(eagerInitializationAnnotation) == true -> context.staticContext.eagerInitializerBlock
else -> context.staticContext.initializerBlock
}
initializerBlock.statements += jsAssignment(fieldName.makeRef(), initializer).makeStmt()
} }
return JsVars(JsVars.JsVar(fieldName)) return JsVars(JsVars.JsVar(fieldName))
@@ -47,10 +47,11 @@ val String.safeModuleName: String
val IrModuleFragment.safeName: String val IrModuleFragment.safeName: String
get() = name.asString().safeModuleName get() = name.asString().safeModuleName
fun generateProxyIrModuleWith(safeName: String, externalName: String) = JsIrModule( fun generateProxyIrModuleWith(safeName: String, externalName: String, importedWithEffectInModuleWithName: String? = null) = JsIrModule(
safeName, safeName,
externalName, externalName,
listOf(JsIrProgramFragment(safeName, "<proxy-file>")) listOf(JsIrProgramFragment(safeName, "<proxy-file>")),
importedWithEffectInModuleWithName = importedWithEffectInModuleWithName
) )
enum class JsGenerationGranularity { enum class JsGenerationGranularity {
@@ -264,15 +265,25 @@ class IrModuleToJsTransformer(
} }
private fun generateJsIrProgramPerFile(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsIrProgram { private fun generateJsIrProgramPerFile(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsIrProgram {
val mainModule = exportData.last()
var someModuleHasEffect = false
val modulesPerFile = buildList { val modulesPerFile = buildList {
for (module in exportData) { for (module in exportData) {
var hasModuleLevelEffect = false
var hasFileWithJsExportedDeclaration = false var hasFileWithJsExportedDeclaration = false
for (fileExports in module.files) { for (fileExports in module.files) {
if (fileExports.file.couldBeSkipped()) continue if (fileExports.file.couldBeSkipped()) continue
val programFragments = generateProgramFragment(fileExports, mode) 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 { programFragments.exportFragment?.let {
add(fileExports.toJsIrModuleForExport(module, it)) add(fileExports.toJsIrModuleForExport(module, it))
@@ -280,8 +291,8 @@ class IrModuleToJsTransformer(
} }
} }
if (hasFileWithJsExportedDeclaration) { if (hasFileWithJsExportedDeclaration || hasModuleLevelEffect || (module === mainModule && someModuleHasEffect)) {
add(module.toJsIrProxyModule()) add(module.toJsIrProxyModule(mainModule.fragment.safeName.takeIf { module !== mainModule && hasModuleLevelEffect }))
} }
} }
} }
@@ -289,11 +300,12 @@ class IrModuleToJsTransformer(
return JsIrProgram(modulesPerFile) return JsIrProgram(modulesPerFile)
} }
private fun IrFileExports.toJsIrModule(programFragment: JsIrProgramFragment): JsIrModule { private fun IrFileExports.toJsIrModule(module: IrAndExportedDeclarations, programFragment: JsIrProgramFragment): JsIrModule {
return JsIrModule( return JsIrModule(
moduleFragmentToNameMapper.getSafeNameFor(file), moduleFragmentToNameMapper.getSafeNameFor(file),
moduleFragmentToNameMapper.getExternalNameFor(file), moduleFragmentToNameMapper.getExternalNameFor(file),
listOf(programFragment), 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( return generateProxyIrModuleWith(
fragment.safeName, fragment.safeName,
moduleFragmentToNameMapper.getExternalNameFor(fragment), moduleFragmentToNameMapper.getExternalNameFor(fragment),
importedWithEffectInModuleWithName
) )
} }
@@ -391,6 +404,7 @@ class IrModuleToJsTransformer(
} }
result.initializers.statements += staticContext.initializerBlock.statements result.initializers.statements += staticContext.initializerBlock.statements
result.eagerInitializers.statements += staticContext.eagerInitializerBlock.statements
if (mainArguments != null) { if (mainArguments != null) {
JsMainFunctionDetector(backendContext).getMainFunctionOrNull(fileExports.file)?.let { JsMainFunctionDetector(backendContext).getMainFunctionOrNull(fileExports.file)?.let {
@@ -22,6 +22,7 @@ class JsIrProgramFragment(val name: String, val packageFqn: String) {
var dts: TypeScriptFragment? = null var dts: TypeScriptFragment? = null
val classes = mutableMapOf<JsName, JsIrIcClassModel>() val classes = mutableMapOf<JsName, JsIrIcClassModel>()
val initializers = JsCompositeBlock() val initializers = JsCompositeBlock()
val eagerInitializers = JsCompositeBlock()
var mainFunction: JsStatement? = null var mainFunction: JsStatement? = null
var testFunInvocation: JsStatement? = null var testFunInvocation: JsStatement? = null
var suiteFn: JsName? = null var suiteFn: JsName? = null
@@ -34,6 +35,7 @@ class JsIrModule(
val externalModuleName: String, val externalModuleName: String,
val fragments: List<JsIrProgramFragment>, val fragments: List<JsIrProgramFragment>,
val reexportedInModuleWithName: String? = null, val reexportedInModuleWithName: String? = null,
val importedWithEffectInModuleWithName: String? = null,
) { ) {
fun makeModuleHeader(): JsIrModuleHeader { fun makeModuleHeader(): JsIrModuleHeader {
val nameBindings = mutableMapOf<String, String>() val nameBindings = mutableMapOf<String, String>()
@@ -55,6 +57,7 @@ class JsIrModule(
nameBindings = nameBindings, nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports, optionalCrossModuleImports = optionalCrossModuleImports,
reexportedInModuleWithName = reexportedInModuleWithName.takeIf { hasDeclarationsToReexport }, reexportedInModuleWithName = reexportedInModuleWithName.takeIf { hasDeclarationsToReexport },
importedWithEffectInModuleWithName = importedWithEffectInModuleWithName,
associatedModule = this associatedModule = this
) )
} }
@@ -67,6 +70,7 @@ class JsIrModuleHeader(
val nameBindings: Map<String, String>, val nameBindings: Map<String, String>,
val optionalCrossModuleImports: Set<String>, val optionalCrossModuleImports: Set<String>,
val reexportedInModuleWithName: String? = null, val reexportedInModuleWithName: String? = null,
val importedWithEffectInModuleWithName: String? = null,
var associatedModule: JsIrModule?, var associatedModule: JsIrModule?,
) { ) {
val externalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) { nameBindings.keys - definitions } val externalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) { nameBindings.keys - definitions }
@@ -94,12 +98,14 @@ class JsIrProgram(private var modules: List<JsIrModule>) {
class CrossModuleDependenciesResolver(private val moduleKind: ModuleKind, private val headers: List<JsIrModuleHeader>) { class CrossModuleDependenciesResolver(private val moduleKind: ModuleKind, private val headers: List<JsIrModuleHeader>) {
fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> { fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> {
val reexportModuleToHeader = headers.groupBy { it.reexportedInModuleWithName } val reexportModuleToHeader = headers.groupBy { it.reexportedInModuleWithName }
val importedInModuleWithEffect = headers.groupBy { it.importedWithEffectInModuleWithName }
val headerToBuilder = headers.associateWith { val headerToBuilder = headers.associateWith {
JsIrModuleCrossModuleReferenceBuilder( JsIrModuleCrossModuleReferenceBuilder(
moduleKind, moduleKind,
it, it,
relativeRequirePath, relativeRequirePath,
reexportModuleToHeader[it.moduleName] ?: emptyList(), reexportModuleToHeader[it.moduleName] ?: emptyList(),
importedInModuleWithEffect[it.moduleName] ?: emptyList(),
) )
} }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferenceBuilder>() val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferenceBuilder>()
@@ -143,6 +149,7 @@ private class JsIrModuleCrossModuleReferenceBuilder(
val header: JsIrModuleHeader, val header: JsIrModuleHeader,
val relativeRequirePath: Boolean, val relativeRequirePath: Boolean,
val transitiveExportFrom: List<JsIrModuleHeader>, val transitiveExportFrom: List<JsIrModuleHeader>,
val importWithEffectFrom: List<JsIrModuleHeader>,
) { ) {
val imports = mutableListOf<CrossModuleRef>() val imports = mutableListOf<CrossModuleRef>()
val exports = mutableSetOf<String>() val exports = mutableSetOf<String>()
@@ -186,10 +193,14 @@ private class JsIrModuleCrossModuleReferenceBuilder(
CrossModuleTransitiveExport(import(it).internalName, relativeRequirePath(it) ?: it.externalModuleName) CrossModuleTransitiveExport(import(it).internalName, relativeRequirePath(it) ?: it.externalModuleName)
} }
} }
val importsWithEffect = importWithEffectFrom.map { CrossModuleImportWithEffect(import(it)) }
return CrossModuleReferences( return CrossModuleReferences(
moduleKind, moduleKind,
importedModules.values.toList(), importedModules.values.toList(),
transitiveExport, transitiveExport,
importsWithEffect,
exportNames, exportNames,
resultImports 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) class CrossModuleTransitiveExport(val internalName: JsName, val externalName: String)
fun CrossModuleTransitiveExport.getRequireEsmName() = "$externalName$ESM_EXTENSION" fun CrossModuleTransitiveExport.getRequireEsmName() = "$externalName$ESM_EXTENSION"
@@ -230,6 +242,7 @@ class CrossModuleReferences(
val moduleKind: ModuleKind, val moduleKind: ModuleKind,
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
val transitiveExportFrom: List<CrossModuleTransitiveExport>, // the list of modules which provide their exports for transitive export val transitiveExportFrom: List<CrossModuleTransitiveExport>, // the list of modules which provide their exports for transitive export
val importsWithEffect: List<CrossModuleImportWithEffect>, // the list of modules which provide their effects for import
val exports: Map<String, String>, // tag -> index val exports: Map<String, String>, // tag -> index
val imports: Map<String, CrossModuleImport>, // tag -> import statement val imports: Map<String, CrossModuleImport>, // tag -> import statement
) { ) {
@@ -237,6 +250,8 @@ class CrossModuleReferences(
var jsImports = emptyMap<String, JsStatement>() // tag -> import statement var jsImports = emptyMap<String, JsStatement>() // tag -> import statement
private set private set
val jsImportsWithEffect: List<JsStatement> = importsWithEffect.map { it.generateCrossModuleImportStatement() }
fun initJsImportsForModule(module: JsIrModule) { fun initJsImportsForModule(module: JsIrModule) {
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value } val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
jsImports = imports.entries.associate { 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 { private fun CrossModuleImport.generateImportVariableDeclaration(importedAs: JsName): JsStatement {
val exportRef = JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleExporter.internalName)) val exportRef = JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleExporter.internalName))
return JsVars(JsVars.JsVar(importedAs, exportRef)) return JsVars(JsVars.JsVar(importedAs, exportRef))
@@ -265,7 +291,7 @@ class CrossModuleReferences(
} }
companion object { 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<JsIrProgramFragment>.mainFragment: JsIrProgramFragment get() = first() val List<JsIrProgramFragment>.mainFragment: JsIrProgramFragment get() = first()
val List<JsIrProgramFragment>.exportFragment: JsIrProgramFragment? get() = getOrNull(1) val List<JsIrProgramFragment>.exportFragment: JsIrProgramFragment? get() = getOrNull(1)
val JsIrProgramFragment.hasEffect: Boolean get() = eagerInitializers.statements.isNotEmpty()
@@ -22,6 +22,7 @@ class Merger(
private val isEsModules = moduleKind == ModuleKind.ES private val isEsModules = moduleKind == ModuleKind.ES
private val importStatements = mutableMapOf<String, JsStatement>() private val importStatements = mutableMapOf<String, JsStatement>()
private val importStatementsWithEffect = mutableListOf<JsStatement>()
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>() private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
private val additionalExports = mutableListOf<JsStatement>() private val additionalExports = mutableListOf<JsStatement>()
@@ -57,6 +58,7 @@ class Merger(
} }
rename(f.initializers) rename(f.initializers)
rename(f.eagerInitializers)
f.mainFunction?.let { rename(it) } f.mainFunction?.let { rename(it) }
f.testFunInvocation?.let { rename(it) } f.testFunInvocation?.let { rename(it) }
f.suiteFn?.let { f.suiteFn = rename(it) } f.suiteFn?.let { f.suiteFn = rename(it) }
@@ -68,6 +70,8 @@ class Merger(
importStatements.putIfAbsent(tag, crossModuleJsImport.renameImportedSymbolInternalName(importName)) importStatements.putIfAbsent(tag, crossModuleJsImport.renameImportedSymbolInternalName(importName))
} }
importStatementsWithEffect.addAll(crossModuleReferences.jsImportsWithEffect)
if (crossModuleReferences.exports.isNotEmpty()) { if (crossModuleReferences.exports.isNotEmpty()) {
val internalModuleName = ReservedJsNames.makeInternalModuleName() val internalModuleName = ReservedJsNames.makeInternalModuleName()
@@ -213,7 +217,7 @@ class Merger(
fragments.forEach { fragments.forEach {
moduleBody += it.declarations.statements moduleBody += it.declarations.statements
classModels += it.classes classModels += it.classes
initializerBlock.statements += it.initializers.statements initializerBlock.statements += it.initializers.statements + it.eagerInitializers.statements
polyfillDeclarationBlock.statements += it.polyfills.statements polyfillDeclarationBlock.statements += it.polyfills.statements
} }
@@ -248,7 +252,7 @@ class Merger(
val exportStatements = declareAndCallJsExporter() + additionalExports + transitiveJsExport() val exportStatements = declareAndCallJsExporter() + additionalExports + transitiveJsExport()
val importedJsModules = this.importedModulesMap.values.toList() + this.crossModuleReferences.importedModules 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() val program = JsProgram()
@@ -342,6 +346,7 @@ class Merger(
is JsImport -> JsImport( is JsImport -> JsImport(
module, module,
when (target) { when (target) {
is JsImport.Target.Effect -> JsImport.Target.Effect
is JsImport.Target.All -> JsImport.Target.All(alias = name.makeRef()) is JsImport.Target.All -> JsImport.Target.All(alias = name.makeRef())
is JsImport.Target.Default -> JsImport.Target.Default(name = name.makeRef()) is JsImport.Target.Default -> JsImport.Target.Default(name = name.makeRef())
is JsImport.Target.Elements -> JsImport.Target.Elements( is JsImport.Target.Elements -> JsImport.Target.Elements(
@@ -119,6 +119,7 @@ private fun JsNode.computeScopes(): Scope {
override fun visitImport(import: JsImport) { override fun visitImport(import: JsImport) {
when (val target = import.target) { when (val target = import.target) {
is JsImport.Target.Effect -> {}
is JsImport.Target.All -> target.alias.name?.let { currentScope.declaredNames += it } 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.Default -> target.name.name?.let { currentScope.declaredNames += it }
is JsImport.Target.Elements -> target.elements.forEach { is JsImport.Target.Elements -> target.elements.forEach {
@@ -25,6 +25,7 @@ class JsStaticContext(
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>() val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
val initializerBlock = JsCompositeBlock() val initializerBlock = JsCompositeBlock()
val eagerInitializerBlock = JsCompositeBlock()
val isPerFile: Boolean get() = mode.granularity === JsGenerationGranularity.PER_FILE val isPerFile: Boolean get() = mode.granularity === JsGenerationGranularity.PER_FILE
} }
@@ -37,6 +37,7 @@ object ImportType {
const val ALL = 0 const val ALL = 0
const val ITEMS = 1 const val ITEMS = 1
const val DEFAULT = 2 const val DEFAULT = 2
const val EFFECT = 3
} }
object ExportType { object ExportType {
@@ -94,6 +94,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
readRepeated { declarations.statements += readStatement() } readRepeated { declarations.statements += readStatement() }
readRepeated { initializers.statements += readStatement() } readRepeated { initializers.statements += readStatement() }
readRepeated { eagerInitializers.statements += readStatement() }
readRepeated { exports.statements += readStatement() } readRepeated { exports.statements += readStatement() }
readRepeated { polyfills.statements += readStatement() } readRepeated { polyfills.statements += readStatement() }
@@ -238,6 +239,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
JsImport( JsImport(
readString(), readString(),
when (val type = readByte().toInt()) { when (val type = readByte().toInt()) {
ImportType.EFFECT -> JsImport.Target.Effect
ImportType.ALL -> JsImport.Target.All(nameTable[readInt()].makeRef()) ImportType.ALL -> JsImport.Target.All(nameTable[readInt()].makeRef())
ImportType.DEFAULT -> JsImport.Target.Default(nameTable[readInt()].makeRef()) ImportType.DEFAULT -> JsImport.Target.Default(nameTable[readInt()].makeRef())
ImportType.ITEMS -> JsImport.Target.Elements(readList { ImportType.ITEMS -> JsImport.Target.Elements(readList {
@@ -132,6 +132,7 @@ private class JsIrAstSerializer {
writeCompositeBlock(fragment.declarations) writeCompositeBlock(fragment.declarations)
writeCompositeBlock(fragment.initializers) writeCompositeBlock(fragment.initializers)
writeCompositeBlock(fragment.eagerInitializers)
writeCompositeBlock(fragment.exports) writeCompositeBlock(fragment.exports)
writeCompositeBlock(fragment.polyfills) writeCompositeBlock(fragment.polyfills)
@@ -328,6 +329,9 @@ private class JsIrAstSerializer {
writeString(import.module) writeString(import.module)
when (val target = import.target) { when (val target = import.target) {
is JsImport.Target.Effect -> {
writeByte(ImportType.EFFECT)
}
is JsImport.Target.All -> { is JsImport.Target.All -> {
writeByte(ImportType.ALL) writeByte(ImportType.ALL)
writeInt(internalizeName(target.alias.name!!)) writeInt(internalizeName(target.alias.name!!))
@@ -1404,7 +1404,10 @@ public class JsToStringGenerationVisitor extends JsVisitor {
p.print("}"); p.print("}");
} }
p.print(" from "); if (!(target == JsImport.Target.Effect.INSTANCE)) {
p.print(" from ");
}
p.print(javaScriptString(jsImport.getModule())); p.print(javaScriptString(jsImport.getModule()));
} }
@@ -15,6 +15,7 @@ class JsImport(
get() = (target as Target.Elements).elements get() = (target as Target.Elements).elements
sealed class Target { sealed class Target {
object Effect : Target()
class Elements(val elements: MutableList<Element>) : Target() class Elements(val elements: MutableList<Element>) : Target()
class Default(val name: JsNameRef) : Target() { class Default(val name: JsNameRef) : Target() {
constructor(name: String) : this(JsNameRef(name)) constructor(name: String) : this(JsNameRef(name))
@@ -36,6 +37,7 @@ class JsImport(
override fun acceptChildren(visitor: JsVisitor) { override fun acceptChildren(visitor: JsVisitor) {
when (target) { when (target) {
is Target.Effect -> {}
is Target.All -> visitor.accept(target.alias) is Target.All -> visitor.accept(target.alias)
is Target.Default -> visitor.accept(target.name) is Target.Default -> visitor.accept(target.name)
is Target.Elements -> target.elements.forEach { is Target.Elements -> target.elements.forEach {
@@ -2041,6 +2041,24 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); 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 @Test
@TestMetadata("inheritance.kt") @TestMetadata("inheritance.kt")
public void testInheritance() throws Exception { public void testInheritance() throws Exception {
@@ -2147,6 +2147,24 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest {
runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); 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 @Test
@TestMetadata("inheritance.kt") @TestMetadata("inheritance.kt")
public void testInheritance() throws Exception { public void testInheritance() throws Exception {
@@ -2147,6 +2147,24 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); 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 @Test
@TestMetadata("inheritance.kt") @TestMetadata("inheritance.kt")
public void testInheritance() throws Exception { public void testInheritance() throws Exception {
@@ -2041,6 +2041,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/esModules/crossModuleRefPerFile/constructor.kt"); 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 @Test
@TestMetadata("inheritance.kt") @TestMetadata("inheritance.kt")
public void testInheritance() throws Exception { public void testInheritance() throws Exception {
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -1,10 +1,18 @@
IGNORE_PER_FILE: true
MODULES: lib1, main MODULES: lib1, main
STEP 0: STEP 0:
libs: lib1, main libs: lib1, main
dirty js modules: 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 libs: lib1, main
dirty js modules: lib1 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