[K/JS] Reexport exported declarations in a proxy module file with per-file compilation

This commit is contained in:
Artem Kobzar
2023-08-01 09:42:33 +00:00
committed by Space Team
parent 5ccc302e01
commit 12715c062d
107 changed files with 341 additions and 169 deletions
@@ -6,10 +6,10 @@
package org.jetbrains.kotlin.ir.backend.js.ic
import org.jetbrains.kotlin.backend.common.serialization.cityHash64
import org.jetbrains.kotlin.backend.common.serialization.cityHash64String
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
import org.jetbrains.kotlin.protobuf.CodedInputStream
import org.jetbrains.kotlin.protobuf.CodedOutputStream
import org.jetbrains.kotlin.utils.addToStdlib.butIf
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.io.File
@@ -22,38 +22,50 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private const val CACHED_FILE_D_TS = "file.d.ts"
}
class CachedFileInfo(
val moduleArtifact: ModuleArtifact,
val fileArtifact: SrcFileArtifact,
val isExportFileCachedInfo: Boolean = false,
) : CacheInfo {
var dtsHash: Long? = null
sealed class CachedFileInfo(val moduleArtifact: ModuleArtifact, moduleHeader: JsIrModuleHeader?) : CacheInfo {
var crossFileReferencesHash: ICHash = ICHash()
var exportFileCachedInfo: CachedFileInfo? = null
override lateinit var jsIrHeader: JsIrModuleHeader
final override lateinit var jsIrHeader: JsIrModuleHeader
constructor(
jsIrModuleHeader: JsIrModuleHeader,
moduleArtifact: ModuleArtifact,
fileArtifact: SrcFileArtifact,
isExportFileCachedInfo: Boolean = false,
tsDeclarationsHash: Long? = null,
) : this(moduleArtifact, fileArtifact, isExportFileCachedInfo) {
jsIrHeader = jsIrModuleHeader
dtsHash = tsDeclarationsHash
init {
if (moduleHeader != null) jsIrHeader = moduleHeader
}
val moduleHeaderArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(JS_MODULE_HEADER) }
val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(if (isExportFileCachedInfo) CACHED_EXPORT_FILE_JS else CACHED_FILE_JS) }
val dtsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { runIf(isExportFileCachedInfo) { getArtifactWithName(CACHED_FILE_D_TS) } }
val sourceMapFileArtifact by lazy(LazyThreadSafetyMode.NONE) { runIf(!isExportFileCachedInfo) { getArtifactWithName(CACHED_FILE_JS_MAP) } }
sealed class SerializableCachedFileInfo(
moduleArtifact: ModuleArtifact,
val fileArtifact: SrcFileArtifact,
moduleHeader: JsIrModuleHeader?
) : CachedFileInfo(moduleArtifact, moduleHeader) {
fun getArtifactWithName(name: String): File? = moduleArtifact.artifactsDir?.let { File(it, "$filePrefix.$name") }
private val filePrefix by lazy(LazyThreadSafetyMode.NONE) { fileArtifact.srcFilePath.run { "${substringAfterLast('/')}.${cityHash64()}" } }
}
private fun getArtifactWithName(name: String): File? = moduleArtifact.artifactsDir?.let { File(it, "$filePrefix.$name") }
class MainFileCachedInfo(moduleArtifact: ModuleArtifact, fileArtifact: SrcFileArtifact, moduleHeader: JsIrModuleHeader? = null) :
SerializableCachedFileInfo(moduleArtifact, fileArtifact, moduleHeader) {
var exportFileCachedInfo: ExportFileCachedInfo? = null
private val filePrefix by lazy(LazyThreadSafetyMode.NONE) {
val pathHash = fileArtifact.srcFilePath.cityHash64String()
"${fileArtifact.srcFilePath.substringAfterLast('/')}.$pathHash"
val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_JS) }
val moduleHeaderArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(JS_MODULE_HEADER) }
val sourceMapFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_JS_MAP) }
}
class ExportFileCachedInfo(
moduleArtifact: ModuleArtifact,
fileArtifact: SrcFileArtifact,
moduleHeader: JsIrModuleHeader? = null,
var tsDeclarationsHash: Long? = null
) : SerializableCachedFileInfo(moduleArtifact, fileArtifact, moduleHeader) {
val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_EXPORT_FILE_JS) }
val dtsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_D_TS) }
}
class ModuleProxyFileCachedInfo(moduleArtifact: ModuleArtifact, moduleHeader: JsIrModuleHeader? = null) :
CachedFileInfo(moduleArtifact, moduleHeader) {
val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_JS) }
val dtsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_D_TS) }
val moduleHeaderArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(JS_MODULE_HEADER) }
private fun getArtifactWithName(name: String): File? = moduleArtifact.artifactsDir?.let { File(it, "entry.$name") }
}
}
@@ -66,13 +78,14 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private fun JsIrProgramFragment.getExportFragmentExternalName(moduleArtifact: ModuleArtifact) =
moduleFragmentToExternalName.getExternalNameForExporterFile(name, packageFqn, moduleArtifact.moduleExternalName)
private fun JsIrProgramFragment.asIrModuleHeader(moduleName: String): JsIrModuleHeader {
private fun JsIrProgramFragment.asIrModuleHeader(moduleName: String, reexportedIn: String? = null): JsIrModuleHeader {
return JsIrModuleHeader(
moduleName = moduleName,
externalModuleName = moduleName,
definitions = definitions,
nameBindings = nameBindings.mapValues { v -> v.value.toString() },
optionalCrossModuleImports = optionalCrossModuleImports,
reexportedInModuleWithName = reexportedIn,
associatedModule = null
)
}
@@ -80,15 +93,20 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()) {
LoadedJsIrModuleHeaders(
mainFragment.run { asIrModuleHeader(getMainFragmentExternalName(moduleArtifact)) },
exportFragment?.run { asIrModuleHeader(mainFragment.getExportFragmentExternalName(moduleArtifact)) },
exportFragment?.run { asIrModuleHeader(mainFragment.getExportFragmentExternalName(moduleArtifact), moduleArtifact.moduleExternalName) },
)
}
private fun CodedInputStream.loadSingleCachedFileInfo(cachedFileInfo: CachedFileInfo) = cachedFileInfo.also {
private fun <T : CachedFileInfo> CodedInputStream.loadSingleCachedFileInfo(cachedFileInfo: T): T = cachedFileInfo.also {
val moduleName = readString()
var reexportedIn: String? = null
it.crossFileReferencesHash = ICHash.fromProtoStream(this)
it.dtsHash = runIf(readBool()) { readInt64() }
if (it is CachedFileInfo.ExportFileCachedInfo) {
it.tsDeclarationsHash = runIf(readBool()) { readInt64() }
reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName
}
val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames()
@@ -98,15 +116,16 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
definitions = definitions,
nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports,
associatedModule = null
reexportedInModuleWithName = reexportedIn,
associatedModule = null,
)
}
private fun <T> CachedFileInfo.readModuleHeaderCache(f: CodedInputStream.() -> T): T? = moduleHeaderArtifact?.useCodedInputIfExists(f)
private fun <T> CachedFileInfo.MainFileCachedInfo.readModuleHeaderCache(f: CodedInputStream.() -> T): T? =
moduleHeaderArtifact?.useCodedInputIfExists(f)
private fun ModuleArtifact.fetchFileInfoFor(fileArtifact: SrcFileArtifact): List<CachedFileInfo>? {
val moduleArtifact = this
val mainFileCachedFileInfo = CachedFileInfo(moduleArtifact, fileArtifact)
val mainFileCachedFileInfo = CachedFileInfo.MainFileCachedInfo(this, fileArtifact)
return mainFileCachedFileInfo.readModuleHeaderCache {
mainFileCachedFileInfo.run {
@@ -117,48 +136,66 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
}
}
private fun CodedInputStream.fetchFileInfoForExportedPart(mainCachedFileInfo: CachedFileInfo): CachedFileInfo? {
private fun ModuleArtifact.fetchModuleProxyFileInfo(): CachedFileInfo.ModuleProxyFileCachedInfo? {
val mainFileCachedFileInfo = CachedFileInfo.ModuleProxyFileCachedInfo(this)
return mainFileCachedFileInfo.moduleHeaderArtifact?.useCodedInputIfExists { loadSingleCachedFileInfo(mainFileCachedFileInfo) }
}
private fun CodedInputStream.fetchFileInfoForExportedPart(mainCachedFileInfo: CachedFileInfo.MainFileCachedInfo): CachedFileInfo.ExportFileCachedInfo? {
return ifTrue {
loadSingleCachedFileInfo(
CachedFileInfo(
mainCachedFileInfo.moduleArtifact,
mainCachedFileInfo.fileArtifact,
isExportFileCachedInfo = true
)
CachedFileInfo.ExportFileCachedInfo(mainCachedFileInfo.moduleArtifact, mainCachedFileInfo.fileArtifact)
)
}
}
private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo) {
private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo.SerializableCachedFileInfo) {
writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName)
cachedFileInfo.crossFileReferencesHash.toProtoStream(this)
ifNotNull(cachedFileInfo.dtsHash, ::writeInt64NoTag)
if (cachedFileInfo is CachedFileInfo.ExportFileCachedInfo) {
ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag)
}
commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader)
}
private fun CachedFileInfo.commitFileInfo() = runIf(!isExportFileCachedInfo) {
moduleHeaderArtifact?.useCodedOutput {
ifNotNull(exportFileCachedInfo) { commitSingleFileInfo(it) }
commitSingleFileInfo(this@commitFileInfo)
private fun CachedFileInfo.commitFileInfo() = when (this) {
is CachedFileInfo.MainFileCachedInfo -> {
moduleHeaderArtifact?.useCodedOutput {
ifNotNull(exportFileCachedInfo) { commitSingleFileInfo(it) }
commitSingleFileInfo(this@commitFileInfo)
}
}
is CachedFileInfo.ModuleProxyFileCachedInfo -> {
moduleHeaderArtifact?.useCodedOutput {
writeStringNoTag(jsIrHeader.externalModuleName)
crossFileReferencesHash.toProtoStream(this)
commitJsIrModuleHeaderNames(jsIrHeader)
}
}
is CachedFileInfo.ExportFileCachedInfo -> {}
}
private fun ModuleArtifact.generateModuleProxyFileCachedInfo(): CachedFileInfo {
return CachedFileInfo.ModuleProxyFileCachedInfo(
this,
generateProxyIrModuleWith(moduleExternalName, moduleExternalName).makeModuleHeader()
)
}
private fun ModuleArtifact.loadFileInfoFor(fileArtifact: SrcFileArtifact): List<CachedFileInfo> {
val moduleArtifact = this
val headers = fileArtifact.loadJsIrModuleHeaders(moduleArtifact)
val headers = fileArtifact.loadJsIrModuleHeaders(this)
val mainCachedFileInfo = CachedFileInfo(headers.mainHeader, this, fileArtifact)
val mainCachedFileInfo = CachedFileInfo.MainFileCachedInfo(this, fileArtifact, headers.mainHeader)
if (headers.exportHeader != null) {
val tsDeclarationsHash = fileArtifact.loadJsIrFragments().exportFragment?.dts?.raw?.cityHash64()
val cachedExportFileInfo = mainCachedFileInfo.readModuleHeaderCache { fetchFileInfoForExportedPart(mainCachedFileInfo) }
mainCachedFileInfo.exportFileCachedInfo = if (cachedExportFileInfo?.dtsHash != tsDeclarationsHash) {
CachedFileInfo(
headers.exportHeader,
moduleArtifact,
mainCachedFileInfo.exportFileCachedInfo = if (cachedExportFileInfo?.tsDeclarationsHash != tsDeclarationsHash) {
CachedFileInfo.ExportFileCachedInfo(
this,
fileArtifact,
tsDeclarationsHash = tsDeclarationsHash,
isExportFileCachedInfo = true
headers.exportHeader,
tsDeclarationsHash,
)
} else {
cachedExportFileInfo
@@ -169,7 +206,11 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
}
private val CachedFileInfo.cachedFiles: CachedFileArtifacts?
get() = jsFileArtifact?.let { CachedFileArtifacts(it, sourceMapFileArtifact, dtsFileArtifact) }
get() = when (this) {
is CachedFileInfo.MainFileCachedInfo -> jsFileArtifact?.let { CachedFileArtifacts(it, sourceMapFileArtifact, null) }
is CachedFileInfo.ExportFileCachedInfo -> jsFileArtifact?.let { CachedFileArtifacts(it, null, dtsFileArtifact) }
is CachedFileInfo.ModuleProxyFileCachedInfo -> jsFileArtifact?.let { CachedFileArtifacts(it, null, dtsFileArtifact) }
}
override fun getMainModuleAndDependencies(cacheInfo: List<CachedFileInfo>) = null to cacheInfo
@@ -188,23 +229,40 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
} ?: compilationOutputs
override fun loadJsIrModule(cacheInfo: CachedFileInfo): JsIrModule {
if (cacheInfo !is CachedFileInfo.SerializableCachedFileInfo) {
return generateProxyIrModuleWith(cacheInfo.jsIrHeader.externalModuleName, cacheInfo.jsIrHeader.externalModuleName)
}
val fragments = cacheInfo.fileArtifact.loadJsIrFragments()
val isExportFileCachedInfo = cacheInfo is CachedFileInfo.ExportFileCachedInfo
return JsIrModule(
cacheInfo.jsIrHeader.moduleName,
cacheInfo.jsIrHeader.externalModuleName,
listOf(if (cacheInfo.isExportFileCachedInfo) fragments.exportFragment!! else fragments.mainFragment)
listOf(if (isExportFileCachedInfo) fragments.exportFragment!! else fragments.mainFragment),
runIf(isExportFileCachedInfo) { cacheInfo.moduleArtifact.moduleSafeName }
)
}
override fun loadProgramHeadersFromCache(): List<CachedFileInfo> {
return moduleArtifacts
.flatMap { module ->
module.fileArtifacts.flatMap {
if (it.isModified())
module.loadFileInfoFor(it)
else
module.fetchFileInfoFor(it) ?: module.loadFileInfoFor(it)
}
.flatMap { moduleArtifact ->
var hasFileWithJsExportedDeclaration = false
moduleArtifact.fileArtifacts
.flatMap { srcFileArtifact ->
val cachedFileInfo = if (srcFileArtifact.isModified())
moduleArtifact.loadFileInfoFor(srcFileArtifact)
else
moduleArtifact.fetchFileInfoFor(srcFileArtifact) ?: moduleArtifact.loadFileInfoFor(srcFileArtifact)
if (!hasFileWithJsExportedDeclaration && cachedFileInfo.hasExportFile()) {
hasFileWithJsExportedDeclaration = true
}
cachedFileInfo
}
.butIf(hasFileWithJsExportedDeclaration) {
it.plus(moduleArtifact.fetchModuleProxyFileInfo() ?: moduleArtifact.generateModuleProxyFileCachedInfo())
}
}
.onEach { headerToCachedInfo[it.jsIrHeader] = it }
}
@@ -228,4 +286,6 @@ class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMult
private data class CachedFileArtifacts(val jsCodeFile: File, val sourceMapFile: File?, val tsDeclarationsFile: File?)
private data class LoadedJsIrModuleHeaders(val mainHeader: JsIrModuleHeader, val exportHeader: JsIrModuleHeader?)
}
private fun List<JsPerFileCache.CachedFileInfo>.hasExportFile(): Boolean = size > 1
}
@@ -47,6 +47,12 @@ val String.safeModuleName: String
val IrModuleFragment.safeName: String
get() = name.asString().safeModuleName
fun generateProxyIrModuleWith(safeName: String, externalName: String) = JsIrModule(
safeName,
externalName,
listOf(JsIrProgramFragment(safeName, "<proxy-file>"))
)
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
@@ -301,10 +307,9 @@ class IrModuleToJsTransformer(
}
private fun IrAndExportedDeclarations.toJsIrProxyModule(): JsIrModule {
return JsIrModule(
return generateProxyIrModuleWith(
fragment.safeName,
moduleFragmentToNameMapper.getExternalNameFor(fragment),
listOf(JsIrProgramFragment(fragment.safeName, "<proxy-file>"))
)
}