Skip to content

Commit

Permalink
[K/JS] Rework ES modules part with squashed JsImport and right renami…
Browse files Browse the repository at this point in the history
…ng strategy inside import/export statements
  • Loading branch information
JSMonk authored and Space Team committed Apr 19, 2023
1 parent 153d7b9 commit 9f94142
Show file tree
Hide file tree
Showing 28 changed files with 382 additions and 257 deletions.
Expand Up @@ -90,6 +90,7 @@ protected void addPlatformOptions(@NotNull List<String> $self, @NotNull K2JSComp
moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS);
moduleKindMap.put(K2JsArgumentConstants.MODULE_AMD, ModuleKind.AMD);
moduleKindMap.put(K2JsArgumentConstants.MODULE_UMD, ModuleKind.UMD);
moduleKindMap.put(K2JsArgumentConstants.MODULE_ES, ModuleKind.ES);

sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS, SourceMapSourceEmbedding.ALWAYS);
sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER, SourceMapSourceEmbedding.NEVER);
Expand Down
Expand Up @@ -44,6 +44,8 @@ interface DelegateFactory {
}

object DefaultDelegateFactory : DelegateFactory {
fun <K : IrDeclaration, V> newDeclarationToValueMapping(): Mapping.Delegate<K, V> = newMappingImpl()

override fun <K : IrDeclaration, V : IrDeclaration> newDeclarationToDeclarationMapping(): Mapping.Delegate<K, V> = newMappingImpl()

override fun <K : IrDeclaration, V : Collection<IrDeclaration>> newDeclarationToDeclarationCollectionMapping(): Mapping.Delegate<K, V> = newMappingImpl()
Expand Down
Expand Up @@ -11,8 +11,8 @@ import org.jetbrains.kotlin.ir.backend.js.utils.MutableReference
import org.jetbrains.kotlin.ir.declarations.*

class JsMapping : DefaultMapping() {
val esClassWhichNeedBoxParameters = mutableSetOf<IrClass>()
val esClassToPossibilityForOptimization = mutableMapOf<IrClass, MutableReference<Boolean>>()
val esClassWhichNeedBoxParameters = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, Boolean>()
val esClassToPossibilityForOptimization = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, MutableReference<Boolean>>()

val outerThisFieldSymbols = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrField>()
val innerClassConstructors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrConstructor>()
Expand Down
Expand Up @@ -83,7 +83,7 @@ class ExportModelToJsStatements(
namespace != null ->
listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt())

esModules -> listOf(JsExport(name, alias = JsName(declaration.name, false)))
esModules -> listOf(JsExport(name.makeRef(), alias = JsName(declaration.name, false)))
else -> emptyList()
}
}
Expand All @@ -96,7 +96,7 @@ class ExportModelToJsStatements(
when {
namespace == null -> {
val property = declaration.generateTopLevelGetters()
listOf(JsVars(property), JsExport(property.name, JsName(declaration.name, false)))
listOf(JsVars(property), JsExport(property.name.makeRef(), JsName(declaration.name, false)))
}
es6mode && declaration.isMember -> {
val jsClass = parentClass?.getCorrespondingJsClass() ?: error("Expect to have parentClass at this point")
Expand Down Expand Up @@ -168,7 +168,7 @@ class ExportModelToJsStatements(
}
val klassExport = when {
namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
esModules -> JsExport(name, alias = JsName(declaration.name, false))
esModules -> JsExport(name.makeRef(), alias = JsName(declaration.name, false))
else -> null
}

Expand Down
Expand Up @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.library.impl.buffer
import org.jetbrains.kotlin.protobuf.CodedInputStream
import org.jetbrains.kotlin.protobuf.CodedOutputStream
import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.security.MessageDigest

internal fun Hash128Bits.toProtoStream(out: CodedOutputStream) {
Expand Down Expand Up @@ -158,6 +159,13 @@ internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalcul
val import = imports[tag]!!
update(tag)
update(import.exportedAs)
update(import.moduleExporter.toString())

if (moduleKind == ModuleKind.ES) {
update(import.moduleExporter.internalName.toString())
update(import.moduleExporter.externalName)
update(import.moduleExporter.relativeRequirePath ?: "")
} else {
update(import.moduleExporter.internalName.toString())
}
}
}.finalize()
Expand Up @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd

class ES6ConstructorBoxParameterOptimizationLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
private val IrClass.needsOfBoxParameter by context.mapping.esClassWhichNeedBoxParameters

override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.es6mode) return
Expand Down Expand Up @@ -85,12 +85,12 @@ class ES6ConstructorBoxParameterOptimizationLowering(private val context: JsIrBa
}

private fun IrClass.requiredToHaveBoxParameter(): Boolean {
return esClassWhichNeedBoxParameters.contains(this)
return needsOfBoxParameter == true
}
}

class ES6CollectConstructorsWhichNeedBoxParameters(private val context: JsIrBackendContext) : DeclarationTransformer {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
private var IrClass.needsOfBoxParameter by context.mapping.esClassWhichNeedBoxParameters

override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.es6mode || declaration !is IrClass) return null
Expand Down Expand Up @@ -134,7 +134,7 @@ class ES6CollectConstructorsWhichNeedBoxParameters(private val context: JsIrBack

private fun IrClass.addToClassListWhichNeedBoxParameter() {
if (isExternal) return
esClassWhichNeedBoxParameters.add(this)
needsOfBoxParameter = true
superClass?.addToClassListWhichNeedBoxParameter()
}
}
Expand Up @@ -177,16 +177,16 @@ class ES6PrimaryConstructorUsageOptimizationLowering(private val context: JsIrBa
* Otherwise, we can generate a simple ES-class constructor in each class of the hierarchy
*/
class ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
private val esClassToPossibilityForOptimization = context.mapping.esClassToPossibilityForOptimization
private val IrClass.needsOfBoxParameter by context.mapping.esClassWhichNeedBoxParameters
private var IrClass.possibilityToOptimizeForEsClass by context.mapping.esClassToPossibilityForOptimization

override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (
context.es6mode &&
declaration is IrClass &&
!declaration.isExternal &&
!context.inlineClassesUtils.isClassInlineLike(declaration) &&
!esClassToPossibilityForOptimization.contains(declaration)
declaration.possibilityToOptimizeForEsClass == null
) {
declaration.checkIfCanBeOptimized()
}
Expand All @@ -199,7 +199,7 @@ class ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering(private val con
var nearestOptimizationDecision: MutableReference<Boolean>? = null

while (currentClass != null && !currentClass.isExternal) {
val currentClassOptimizationDecision = esClassToPossibilityForOptimization[currentClass]
val currentClassOptimizationDecision = currentClass.possibilityToOptimizeForEsClass

if (currentClassOptimizationDecision != null) {
nearestOptimizationDecision = currentClassOptimizationDecision
Expand All @@ -214,8 +214,8 @@ class ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering(private val con
}

currentClass = this
while (currentClass != null && !currentClass.isExternal && !esClassToPossibilityForOptimization.contains(currentClass)) {
esClassToPossibilityForOptimization[currentClass] = nearestOptimizationDecision
while (currentClass != null && !currentClass.isExternal && currentClass.possibilityToOptimizeForEsClass == null) {
currentClass.possibilityToOptimizeForEsClass = nearestOptimizationDecision

if (nearestOptimizationDecision.value && !currentClass.canBeOptimized()) {
nearestOptimizationDecision.value = false
Expand Down Expand Up @@ -249,7 +249,7 @@ class ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering(private val con
}

private fun IrClass.isSubclassOfExternalClassWithRequiredBoxParameter(): Boolean {
return superClass?.isExternal == true && esClassWhichNeedBoxParameters.contains(this)
return superClass?.isExternal == true && needsOfBoxParameter == true
}
}

Expand Down
Expand Up @@ -7,11 +7,19 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs

import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.js.backend.ast.ESM_EXTENSION
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.backend.ast.REGULAR_EXTENSION
import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.io.File
import java.nio.file.Files

val ModuleKind.extension: String
get() = when (this) {
ModuleKind.ES -> ESM_EXTENSION
else -> REGULAR_EXTENSION
}

abstract class CompilationOutputs {
var dependencies: Collection<Pair<String, CompilationOutputs>> = emptyList()

Expand All @@ -35,10 +43,10 @@ abstract class CompilationOutputs {
}

dependencies.forEach { (name, content) ->
outputDir.resolve("$name.js").writeAsJsFile(content)
outputDir.resolve("$name${moduleKind.extension}").writeAsJsFile(content)
}

val outputJsFile = outputDir.resolve("$outputName.js")
val outputJsFile = outputDir.resolve("$outputName${moduleKind.extension}")
outputJsFile.writeAsJsFile(this)

if (genDTS) {
Expand Down
Expand Up @@ -94,12 +94,14 @@ class CrossModuleDependenciesResolver(
private val headers: List<JsIrModuleHeader>
) {
fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> {
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(moduleKind, it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferenceBuilder(moduleKind, it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferenceBuilder>()

val mainModuleHeader = headers.last()
val otherModuleHeaders = headers.dropLast(1)
headerToBuilder[mainModuleHeader]!!.transitiveJsExportFrom = otherModuleHeaders
if (moduleKind != ModuleKind.ES) {
val mainModuleHeader = headers.last()
val otherModuleHeaders = headers.dropLast(1)
headerToBuilder[mainModuleHeader]!!.transitiveJsExportFrom = otherModuleHeaders
}

for (header in headers) {
val builder = headerToBuilder[header]!!
Expand Down Expand Up @@ -130,9 +132,9 @@ class CrossModuleDependenciesResolver(
}
}

private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String)
private class CrossModuleRef(val module: JsIrModuleCrossModuleReferenceBuilder, val tag: String)

private class JsIrModuleCrossModuleReferecenceBuilder(
private class JsIrModuleCrossModuleReferenceBuilder(
val moduleKind: ModuleKind,
val header: JsIrModuleHeader,
val relativeRequirePath: Boolean
Expand All @@ -150,20 +152,15 @@ private class JsIrModuleCrossModuleReferecenceBuilder(

fun buildCrossModuleRefs(): CrossModuleReferences {
buildExportNames()
val isImportOptional = moduleKind == ModuleKind.ES
val importedModules = mutableMapOf<JsIrModuleHeader, JsImportedModule>()

fun import(moduleHeader: JsIrModuleHeader): JsName {
return importedModules.getOrPut(moduleHeader) {
val jsModuleName = JsName(moduleHeader.moduleName, false)
val relativeRequirePath = relativeRequirePath(moduleHeader)

JsImportedModule(
moduleHeader.externalModuleName,
jsModuleName,
null,
relativeRequirePath
)
}.internalName
fun import(moduleHeader: JsIrModuleHeader): JsImportedModule {
return if (isImportOptional) {
moduleHeader.toJsImportedModule()
} else {
importedModules.getOrPut(moduleHeader) { moduleHeader.toJsImportedModule() }
}
}

val resultImports = imports.associate { crossModuleRef ->
Expand All @@ -173,13 +170,13 @@ private class JsIrModuleCrossModuleReferecenceBuilder(
"Cross module dependency resolution failed due to signature '$tag' redefinition"
}
val exportedAs = crossModuleRef.module.exportNames[tag]!!
val moduleName = import(crossModuleRef.module.header)
val importedModule = import(crossModuleRef.module.header)

tag to CrossModuleImport(exportedAs, moduleName)
tag to CrossModuleImport(exportedAs, importedModule)
}

val transitiveExport = transitiveJsExportFrom.mapNotNull {
if (!it.hasJsExports) null else CrossModuleTransitiveExport(import(it), it.externalModuleName)
if (!it.hasJsExports) null else CrossModuleTransitiveExport(import(it).internalName, it.externalModuleName)
}
return CrossModuleReferences(
moduleKind,
Expand All @@ -190,12 +187,22 @@ private class JsIrModuleCrossModuleReferecenceBuilder(
)
}

private fun JsIrModuleHeader.toJsImportedModule(): JsImportedModule {
val jsModuleName = JsName(moduleName, false)
val relativeRequirePath = relativeRequirePath(this)

return JsImportedModule(
externalModuleName,
jsModuleName,
null,
relativeRequirePath
)
}

private fun relativeRequirePath(moduleHeader: JsIrModuleHeader): String? {
if (!this.relativeRequirePath) return null

val parentMain = File(header.externalModuleName).parentFile

if (parentMain == null) return "./${moduleHeader.externalModuleName}"
val parentMain = File(header.externalModuleName).parentFile ?: return "./${moduleHeader.externalModuleName}"

val relativePath = File(moduleHeader.externalModuleName)
.toRelativeString(parentMain)
Expand All @@ -206,10 +213,12 @@ private class JsIrModuleCrossModuleReferecenceBuilder(
}
}

class CrossModuleImport(val exportedAs: String, val moduleExporter: JsName)
class CrossModuleImport(val exportedAs: String, val moduleExporter: JsImportedModule)

class CrossModuleTransitiveExport(val internalName: JsName, val externalName: String)

fun CrossModuleTransitiveExport.getRequireEsmName() = "$externalName$ESM_EXTENSION"

class CrossModuleReferences(
val moduleKind: ModuleKind,
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
Expand All @@ -218,28 +227,45 @@ class CrossModuleReferences(
val imports: Map<String, CrossModuleImport>, // tag -> import statement
) {
// built from imports
var jsImports = emptyMap<String, JsVars.JsVar>() // tag -> import statement
var jsImports = emptyMap<String, JsStatement>() // tag -> import statement
private set

fun initJsImportsForModule(module: JsIrModule) {
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
jsImports = imports.entries.associate {
val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for signature ${it.key}")
val exportRef = JsNameRef(
it.value.exportedAs,
it.value.moduleExporter.let {
if (moduleKind == ModuleKind.ES) {
it.makeRef()
} else {
ReservedJsNames.makeCrossModuleNameRef(it)
}
}
)
it.key to JsVars.JsVar(importedAs, exportRef)
it.key to it.value.generateCrossModuleImportStatement(importedAs)
}
}

private fun CrossModuleImport.generateCrossModuleImportStatement(importedAs: JsName): JsStatement {
return when (moduleKind) {
ModuleKind.ES -> generateJsImportStatement(importedAs)
else -> generateImportVariableDeclaration(importedAs)
}
}

private fun CrossModuleImport.generateImportVariableDeclaration(importedAs: JsName): JsStatement {
val exportRef = JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleExporter.internalName))
return JsVars(JsVars.JsVar(importedAs, exportRef))
}

private fun CrossModuleImport.generateJsImportStatement(importedAs: JsName): JsStatement {
return JsImport(
moduleExporter.getRequireName(true),
JsImport.Element(JsName(exportedAs, false), importedAs.makeRef())
)
}

companion object {
fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyMap(), emptyMap())
}
}

fun JsStatement.renameImportedSymbolInternalName(newName: JsName): JsStatement {
return when (this) {
is JsImport -> JsImport(module, JsImport.Element((target as JsImport.Target.Elements).elements.single().name, newName.makeRef()))
is JsVars -> JsVars(JsVars.JsVar(newName, vars.single().initExpression))
else -> error("Unexpected cross-module import statement ${this::class.qualifiedName}")
}
}

0 comments on commit 9f94142

Please sign in to comment.