[K/JS] Implement an incremental compilation for the per-file granularity
This commit is contained in:
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
import org.jetbrains.kotlin.backend.common.CommonKLibResolver
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||
import org.jetbrains.kotlin.backend.common.serialization.IrInterningService
|
||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64
|
||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64String
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.backend.js.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
|
||||
@@ -128,7 +128,7 @@ class CacheUpdater(
|
||||
private val incrementalCaches = libraryDependencies.keys.associate { lib ->
|
||||
val libFile = KotlinLibraryFile(lib)
|
||||
val file = File(libFile.path)
|
||||
val pathHash = file.absolutePath.cityHash64().toULong().toString(Character.MAX_RADIX)
|
||||
val pathHash = file.absolutePath.cityHash64String()
|
||||
val libraryCacheDir = File(cacheRootDir, "${file.name}.$pathHash")
|
||||
libFile to IncrementalCache(KotlinLoadedLibraryHeader(lib, internationService), libraryCacheDir)
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64
|
||||
import org.jetbrains.kotlin.backend.common.serialization.FingerprintHash
|
||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64String
|
||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||
import org.jetbrains.kotlin.protobuf.CodedInputStream
|
||||
import org.jetbrains.kotlin.protobuf.CodedOutputStream
|
||||
@@ -85,7 +85,7 @@ internal class IncrementalCache(private val library: KotlinLibraryHeader, val ca
|
||||
) : KotlinSourceFileMetadata()
|
||||
|
||||
private fun KotlinSourceFile.getCacheFile(suffix: String): File {
|
||||
val pathHash = path.cityHash64().toULong().toString(Character.MAX_RADIX)
|
||||
val pathHash = path.cityHash64String()
|
||||
return File(cacheDir, "${File(path).name}.$pathHash.$suffix")
|
||||
}
|
||||
|
||||
|
||||
+24
-16
@@ -29,7 +29,8 @@ class JsExecutableProducer(
|
||||
fun buildExecutable(granularity: JsGenerationGranularity, outJsProgram: Boolean) =
|
||||
when (granularity) {
|
||||
JsGenerationGranularity.WHOLE_PROGRAM -> buildSingleModuleExecutable(outJsProgram)
|
||||
JsGenerationGranularity.PER_MODULE, JsGenerationGranularity.PER_FILE -> buildMultiModuleExecutable(outJsProgram)
|
||||
JsGenerationGranularity.PER_MODULE -> buildMultiArtifactExecutable(outJsProgram, JsPerModuleCache(caches))
|
||||
JsGenerationGranularity.PER_FILE -> buildMultiArtifactExecutable(outJsProgram, JsPerFileCache(caches))
|
||||
}
|
||||
|
||||
private fun buildSingleModuleExecutable(outJsProgram: Boolean): BuildResult {
|
||||
@@ -45,29 +46,33 @@ class JsExecutableProducer(
|
||||
return BuildResult(out, listOf(mainModuleName))
|
||||
}
|
||||
|
||||
private fun buildMultiModuleExecutable(outJsProgram: Boolean): BuildResult {
|
||||
private fun <CacheInfo : JsMultiArtifactCache.CacheInfo> buildMultiArtifactExecutable(
|
||||
outJsProgram: Boolean,
|
||||
jsMultiArtifactCache: JsMultiArtifactCache<CacheInfo>
|
||||
): BuildResult {
|
||||
val rebuildModules = mutableListOf<String>()
|
||||
stopwatch.startNext("JS code cache loading")
|
||||
val jsMultiModuleCache = JsMultiModuleCache(caches)
|
||||
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
|
||||
val cachedProgram = jsMultiArtifactCache.loadProgramHeadersFromCache()
|
||||
|
||||
stopwatch.startNext("Cross module references resolving")
|
||||
val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader })
|
||||
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
|
||||
|
||||
stopwatch.startNext("Loading JS IR modules with updated cross module references")
|
||||
jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences)
|
||||
jsMultiArtifactCache.loadRequiredJsIrModules(crossModuleReferences)
|
||||
|
||||
fun CacheInfo?.compileModule(moduleName: String, generateCallToMain: Boolean): CompilationOutputs {
|
||||
if (this == null) return jsMultiArtifactCache.fetchCompiledJsCodeForNullCacheInfo()
|
||||
|
||||
fun JsMultiModuleCache.CachedModuleInfo.compileModule(moduleName: String, generateCallToMain: Boolean): CompilationOutputs {
|
||||
if (jsIrHeader.associatedModule == null) {
|
||||
stopwatch.startNext("Fetching cached JS code")
|
||||
val compilationOutputs = jsMultiModuleCache.fetchCompiledJsCode(artifact)
|
||||
val compilationOutputs = jsMultiArtifactCache.fetchCompiledJsCode(this)
|
||||
if (compilationOutputs != null) {
|
||||
return compilationOutputs
|
||||
}
|
||||
// theoretically should never happen
|
||||
stopwatch.startNext("Loading JS IR modules")
|
||||
jsIrHeader.associatedModule = artifact.loadJsIrModule()
|
||||
jsIrHeader.associatedModule = jsMultiArtifactCache.loadJsIrModule(this)
|
||||
}
|
||||
stopwatch.startNext("Initializing JS imports")
|
||||
val associatedModule = jsIrHeader.associatedModule ?: icError("can not load module $moduleName")
|
||||
@@ -87,17 +92,20 @@ class JsExecutableProducer(
|
||||
|
||||
stopwatch.startNext("Committing compiled JS code")
|
||||
rebuildModules += moduleName
|
||||
return jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule)
|
||||
return jsMultiArtifactCache.commitCompiledJsCode(this, compiledModule)
|
||||
}
|
||||
|
||||
val cachedMainModule = cachedProgram.last()
|
||||
val mainModule = cachedMainModule.compileModule(mainModuleName, true)
|
||||
val (cachedMainModule, cachedOtherModules) = jsMultiArtifactCache.getMainModuleAndDependencies(cachedProgram)
|
||||
|
||||
val mainModuleCompilationOutput = cachedMainModule
|
||||
.compileModule(mainModuleName, true)
|
||||
.apply {
|
||||
dependencies = cachedOtherModules.map {
|
||||
it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false)
|
||||
}
|
||||
}
|
||||
|
||||
val cachedOtherModules = cachedProgram.dropLast(1)
|
||||
mainModule.dependencies = cachedOtherModules.map {
|
||||
it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false)
|
||||
}
|
||||
stopwatch.stop()
|
||||
return BuildResult(mainModule, rebuildModules)
|
||||
return BuildResult(mainModuleCompilationOutput, rebuildModules)
|
||||
}
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
|
||||
import org.jetbrains.kotlin.protobuf.CodedInputStream
|
||||
import org.jetbrains.kotlin.protobuf.CodedOutputStream
|
||||
import java.io.File
|
||||
|
||||
abstract class JsMultiArtifactCache<T : JsMultiArtifactCache.CacheInfo> {
|
||||
abstract fun loadProgramHeadersFromCache(): List<T>
|
||||
abstract fun loadRequiredJsIrModules(crossModuleReferences: Map<JsIrModuleHeader, CrossModuleReferences>)
|
||||
abstract fun fetchCompiledJsCode(cacheInfo: T): CompilationOutputsCached?
|
||||
abstract fun commitCompiledJsCode(cacheInfo: T, compilationOutputs: CompilationOutputsBuilt): CompilationOutputs
|
||||
abstract fun fetchCompiledJsCodeForNullCacheInfo(): CompilationOutputs
|
||||
abstract fun loadJsIrModule(cacheInfo: T): JsIrModule
|
||||
abstract fun getMainModuleAndDependencies(cacheInfo: List<T>): Pair<T?, List<T>>
|
||||
|
||||
protected fun File.writeIfNotNull(data: String?) {
|
||||
if (data != null) {
|
||||
parentFile?.mkdirs()
|
||||
writeText(data)
|
||||
} else {
|
||||
delete()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun CodedInputStream.fetchJsIrModuleHeaderNames(): JsIrModuleHeaderNames {
|
||||
val definitions = mutableSetOf<String>()
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
|
||||
repeat(readInt32()) {
|
||||
val tag = readString()
|
||||
val mask = readInt32()
|
||||
if (mask and NameType.DEFINITIONS.typeMask != 0) {
|
||||
definitions += tag
|
||||
}
|
||||
if (mask and NameType.OPTIONAL_IMPORTS.typeMask != 0) {
|
||||
optionalCrossModuleImports += tag
|
||||
}
|
||||
if (mask and NameType.NAME_BINDINGS.typeMask != 0) {
|
||||
nameBindings[tag] = readString()
|
||||
}
|
||||
}
|
||||
|
||||
return JsIrModuleHeaderNames(definitions, nameBindings, optionalCrossModuleImports)
|
||||
}
|
||||
|
||||
protected fun CodedOutputStream.commitJsIrModuleHeaderNames(jsIrHeader: JsIrModuleHeader) {
|
||||
val names = mutableMapOf<String, Pair<Int, String?>>()
|
||||
|
||||
for ((tag, name) in jsIrHeader.nameBindings) {
|
||||
names[tag] = NameType.NAME_BINDINGS.typeMask to name
|
||||
}
|
||||
for (tag in jsIrHeader.optionalCrossModuleImports) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.OPTIONAL_IMPORTS.typeMask) to maskAndName?.second
|
||||
}
|
||||
for (tag in jsIrHeader.definitions) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.DEFINITIONS.typeMask) to maskAndName?.second
|
||||
}
|
||||
|
||||
writeInt32NoTag(names.size)
|
||||
|
||||
for ((tag, maskAndName) in names) {
|
||||
writeStringNoTag(tag)
|
||||
writeInt32NoTag(maskAndName.first)
|
||||
if (maskAndName.second != null) {
|
||||
writeStringNoTag(maskAndName.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheInfo {
|
||||
val jsIrHeader: JsIrModuleHeader
|
||||
}
|
||||
|
||||
protected data class JsIrModuleHeaderNames(
|
||||
val definitions: Set<String>,
|
||||
val nameBindings: Map<String, String>,
|
||||
val optionalCrossModuleImports: Set<String>,
|
||||
)
|
||||
|
||||
protected enum class NameType(val typeMask: Int) {
|
||||
DEFINITIONS(0b1),
|
||||
NAME_BINDINGS(0b10),
|
||||
OPTIONAL_IMPORTS(0b100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
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.runIf
|
||||
import java.io.File
|
||||
|
||||
class JsPerFileCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMultiArtifactCache<JsPerFileCache.CachedFileInfo>() {
|
||||
companion object {
|
||||
private const val JS_MODULE_HEADER = "js.module.header.bin"
|
||||
private const val CACHED_FILE_JS = "file.js"
|
||||
private const val CACHED_EXPORT_FILE_JS = "file.export.js"
|
||||
private const val CACHED_FILE_JS_MAP = "file.js.map"
|
||||
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
|
||||
var crossFileReferencesHash: ICHash = ICHash()
|
||||
var exportFileCachedInfo: CachedFileInfo? = null
|
||||
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
|
||||
}
|
||||
|
||||
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) } }
|
||||
|
||||
private fun getArtifactWithName(name: String): File? = moduleArtifact.artifactsDir?.let { File(it, "$filePrefix.$name") }
|
||||
|
||||
private val filePrefix by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val pathHash = fileArtifact.srcFilePath.cityHash64String()
|
||||
"${fileArtifact.srcFilePath.substringAfterLast('/')}.$pathHash"
|
||||
}
|
||||
}
|
||||
|
||||
private val headerToCachedInfo = hashMapOf<JsIrModuleHeader, CachedFileInfo>()
|
||||
private val moduleFragmentToExternalName = ModuleFragmentToExternalName(emptyMap())
|
||||
|
||||
private fun JsIrProgramFragment.getMainFragmentExternalName(moduleArtifact: ModuleArtifact) =
|
||||
moduleFragmentToExternalName.getExternalNameFor(name, packageFqn, moduleArtifact.moduleExternalName)
|
||||
|
||||
private fun JsIrProgramFragment.getExportFragmentExternalName(moduleArtifact: ModuleArtifact) =
|
||||
moduleFragmentToExternalName.getExternalNameForExporterFile(name, packageFqn, moduleArtifact.moduleExternalName)
|
||||
|
||||
private fun JsIrProgramFragment.asIrModuleHeader(moduleName: String): JsIrModuleHeader {
|
||||
return JsIrModuleHeader(
|
||||
moduleName = moduleName,
|
||||
externalModuleName = moduleName,
|
||||
definitions = definitions,
|
||||
nameBindings = nameBindings.mapValues { v -> v.value.toString() },
|
||||
optionalCrossModuleImports = optionalCrossModuleImports,
|
||||
associatedModule = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()) {
|
||||
LoadedJsIrModuleHeaders(
|
||||
mainFragment.run { asIrModuleHeader(getMainFragmentExternalName(moduleArtifact)) },
|
||||
exportFragment?.run { asIrModuleHeader(mainFragment.getExportFragmentExternalName(moduleArtifact)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CodedInputStream.loadSingleCachedFileInfo(cachedFileInfo: CachedFileInfo) = cachedFileInfo.also {
|
||||
val moduleName = readString()
|
||||
|
||||
it.crossFileReferencesHash = ICHash.fromProtoStream(this)
|
||||
it.dtsHash = runIf(readBool()) { readInt64() }
|
||||
|
||||
val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames()
|
||||
|
||||
it.jsIrHeader = JsIrModuleHeader(
|
||||
moduleName = moduleName,
|
||||
externalModuleName = moduleName,
|
||||
definitions = definitions,
|
||||
nameBindings = nameBindings,
|
||||
optionalCrossModuleImports = optionalCrossModuleImports,
|
||||
associatedModule = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> CachedFileInfo.readModuleHeaderCache(f: CodedInputStream.() -> T): T? = moduleHeaderArtifact?.useCodedInputIfExists(f)
|
||||
|
||||
private fun ModuleArtifact.fetchFileInfoFor(fileArtifact: SrcFileArtifact): List<CachedFileInfo>? {
|
||||
val moduleArtifact = this
|
||||
val mainFileCachedFileInfo = CachedFileInfo(moduleArtifact, fileArtifact)
|
||||
|
||||
return mainFileCachedFileInfo.readModuleHeaderCache {
|
||||
mainFileCachedFileInfo.run {
|
||||
exportFileCachedInfo = fetchFileInfoForExportedPart(this)
|
||||
loadSingleCachedFileInfo(this)
|
||||
listOfNotNull(exportFileCachedInfo, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CodedInputStream.fetchFileInfoForExportedPart(mainCachedFileInfo: CachedFileInfo): CachedFileInfo? {
|
||||
return ifTrue {
|
||||
loadSingleCachedFileInfo(
|
||||
CachedFileInfo(
|
||||
mainCachedFileInfo.moduleArtifact,
|
||||
mainCachedFileInfo.fileArtifact,
|
||||
isExportFileCachedInfo = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo) {
|
||||
writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName)
|
||||
cachedFileInfo.crossFileReferencesHash.toProtoStream(this)
|
||||
ifNotNull(cachedFileInfo.dtsHash, ::writeInt64NoTag)
|
||||
commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader)
|
||||
}
|
||||
|
||||
private fun CachedFileInfo.commitFileInfo() = runIf(!isExportFileCachedInfo) {
|
||||
moduleHeaderArtifact?.useCodedOutput {
|
||||
ifNotNull(exportFileCachedInfo) { commitSingleFileInfo(it) }
|
||||
commitSingleFileInfo(this@commitFileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ModuleArtifact.loadFileInfoFor(fileArtifact: SrcFileArtifact): List<CachedFileInfo> {
|
||||
val moduleArtifact = this
|
||||
val headers = fileArtifact.loadJsIrModuleHeaders(moduleArtifact)
|
||||
|
||||
val mainCachedFileInfo = CachedFileInfo(headers.mainHeader, this, fileArtifact)
|
||||
|
||||
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,
|
||||
fileArtifact,
|
||||
tsDeclarationsHash = tsDeclarationsHash,
|
||||
isExportFileCachedInfo = true
|
||||
)
|
||||
} else {
|
||||
cachedExportFileInfo
|
||||
}
|
||||
}
|
||||
|
||||
return listOfNotNull(mainCachedFileInfo.exportFileCachedInfo, mainCachedFileInfo)
|
||||
}
|
||||
|
||||
private val CachedFileInfo.cachedFiles: CachedFileArtifacts?
|
||||
get() = jsFileArtifact?.let { CachedFileArtifacts(it, sourceMapFileArtifact, dtsFileArtifact) }
|
||||
|
||||
override fun getMainModuleAndDependencies(cacheInfo: List<CachedFileInfo>) = null to cacheInfo
|
||||
|
||||
override fun fetchCompiledJsCodeForNullCacheInfo() = PerFileEntryPointCompilationOutput()
|
||||
|
||||
override fun fetchCompiledJsCode(cacheInfo: CachedFileInfo) =
|
||||
cacheInfo.cachedFiles?.let { (jsCodeFile, sourceMapFile, tsDeclarationsFile) ->
|
||||
jsCodeFile.ifExists { this }
|
||||
?.let { CompilationOutputsCached(it, sourceMapFile?.ifExists { this }, tsDeclarationsFile?.ifExists { this }) }
|
||||
}
|
||||
|
||||
override fun commitCompiledJsCode(cacheInfo: CachedFileInfo, compilationOutputs: CompilationOutputsBuilt) =
|
||||
cacheInfo.cachedFiles?.let { (jsCodeFile, jsMapFile, tsDeclarationsFile) ->
|
||||
tsDeclarationsFile?.writeIfNotNull(compilationOutputs.tsDefinitions?.raw)
|
||||
compilationOutputs.writeJsCodeIntoModuleCache(jsCodeFile, jsMapFile)
|
||||
} ?: compilationOutputs
|
||||
|
||||
override fun loadJsIrModule(cacheInfo: CachedFileInfo): JsIrModule {
|
||||
val fragments = cacheInfo.fileArtifact.loadJsIrFragments()
|
||||
return JsIrModule(
|
||||
cacheInfo.jsIrHeader.moduleName,
|
||||
cacheInfo.jsIrHeader.externalModuleName,
|
||||
listOf(if (cacheInfo.isExportFileCachedInfo) fragments.exportFragment!! else fragments.mainFragment)
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
.onEach { headerToCachedInfo[it.jsIrHeader] = it }
|
||||
}
|
||||
|
||||
override fun loadRequiredJsIrModules(crossModuleReferences: Map<JsIrModuleHeader, CrossModuleReferences>) {
|
||||
for ((header, references) in crossModuleReferences) {
|
||||
val cachedInfo = headerToCachedInfo[header] ?: notFoundIcError("artifact for module ${header.moduleName}")
|
||||
|
||||
val actualCrossModuleHash = references.crossModuleReferencesHashForIC()
|
||||
|
||||
if (header.associatedModule == null && cachedInfo.crossFileReferencesHash != actualCrossModuleHash) {
|
||||
header.associatedModule = loadJsIrModule(cachedInfo)
|
||||
}
|
||||
|
||||
header.associatedModule?.let {
|
||||
cachedInfo.crossFileReferencesHash = actualCrossModuleHash
|
||||
cachedInfo.commitFileInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CachedFileArtifacts(val jsCodeFile: File, val sourceMapFile: File?, val tsDeclarationsFile: File?)
|
||||
private data class LoadedJsIrModuleHeaders(val mainHeader: JsIrModuleHeader, val exportHeader: JsIrModuleHeader?)
|
||||
}
|
||||
+21
-63
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
|
||||
import java.io.File
|
||||
|
||||
class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
class JsPerModuleCache(private val moduleArtifacts: List<ModuleArtifact>) : JsMultiArtifactCache<JsPerModuleCache.CachedModuleInfo>() {
|
||||
companion object {
|
||||
private const val JS_MODULE_HEADER = "js.module.header.bin"
|
||||
private const val CACHED_MODULE_JS = "module.js"
|
||||
@@ -16,35 +16,18 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
private const val CACHED_MODULE_D_TS = "module.d.ts"
|
||||
}
|
||||
|
||||
private enum class NameType(val typeMask: Int) {
|
||||
DEFINITIONS(0b1), NAME_BINDINGS(0b10), OPTIONAL_IMPORTS(0b100)
|
||||
}
|
||||
|
||||
class CachedModuleInfo(val artifact: ModuleArtifact, val jsIrHeader: JsIrModuleHeader, var crossModuleReferencesHash: ICHash = ICHash())
|
||||
class CachedModuleInfo(
|
||||
val artifact: ModuleArtifact,
|
||||
override val jsIrHeader: JsIrModuleHeader,
|
||||
var crossModuleReferencesHash: ICHash = ICHash()
|
||||
) : CacheInfo
|
||||
|
||||
private val headerToCachedInfo = hashMapOf<JsIrModuleHeader, CachedModuleInfo>()
|
||||
|
||||
private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, JS_MODULE_HEADER).useCodedInputIfExists {
|
||||
val definitions = mutableSetOf<String>()
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
|
||||
val crossModuleReferencesHash = ICHash.fromProtoStream(this)
|
||||
val reexportedInModuleWithName = ifTrue { readString() }
|
||||
|
||||
repeat(readInt32()) {
|
||||
val tag = readString()
|
||||
val mask = readInt32()
|
||||
if (mask and NameType.DEFINITIONS.typeMask != 0) {
|
||||
definitions += tag
|
||||
}
|
||||
if (mask and NameType.OPTIONAL_IMPORTS.typeMask != 0) {
|
||||
optionalCrossModuleImports += tag
|
||||
}
|
||||
if (mask and NameType.NAME_BINDINGS.typeMask != 0) {
|
||||
nameBindings[tag] = readString()
|
||||
}
|
||||
}
|
||||
val (definitions, nameBindings, optionalCrossModuleImports) = fetchJsIrModuleHeaderNames()
|
||||
|
||||
CachedModuleInfo(
|
||||
artifact = this@fetchModuleInfo,
|
||||
@@ -63,61 +46,36 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
|
||||
private fun CachedModuleInfo.commitModuleInfo() = artifact.artifactsDir?.let { cacheDir ->
|
||||
File(cacheDir, JS_MODULE_HEADER).useCodedOutput {
|
||||
val names = mutableMapOf<String, Pair<Int, String?>>()
|
||||
for ((tag, name) in jsIrHeader.nameBindings) {
|
||||
names[tag] = NameType.NAME_BINDINGS.typeMask to name
|
||||
}
|
||||
for (tag in jsIrHeader.optionalCrossModuleImports) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.OPTIONAL_IMPORTS.typeMask) to maskAndName?.second
|
||||
}
|
||||
for (tag in jsIrHeader.definitions) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.DEFINITIONS.typeMask) to maskAndName?.second
|
||||
}
|
||||
crossModuleReferencesHash.toProtoStream(this)
|
||||
|
||||
ifNotNull(jsIrHeader.reexportedInModuleWithName) {
|
||||
writeStringNoTag(it)
|
||||
}
|
||||
|
||||
writeInt32NoTag(names.size)
|
||||
|
||||
for ((tag, maskAndName) in names) {
|
||||
writeStringNoTag(tag)
|
||||
writeInt32NoTag(maskAndName.first)
|
||||
if (maskAndName.second != null) {
|
||||
writeStringNoTag(maskAndName.second)
|
||||
}
|
||||
}
|
||||
ifNotNull(jsIrHeader.reexportedInModuleWithName) { writeStringNoTag(it) }
|
||||
commitJsIrModuleHeaderNames(jsIrHeader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.writeIfNotNull(data: String?) {
|
||||
if (data != null) {
|
||||
parentFile?.mkdirs()
|
||||
writeText(data)
|
||||
} else {
|
||||
delete()
|
||||
}
|
||||
}
|
||||
override fun loadJsIrModule(cacheInfo: CachedModuleInfo) = cacheInfo.artifact.loadJsIrModule()
|
||||
|
||||
fun fetchCompiledJsCode(artifact: ModuleArtifact) = artifact.artifactsDir?.let { cacheDir ->
|
||||
override fun getMainModuleAndDependencies(cacheInfo: List<CachedModuleInfo>) =
|
||||
cacheInfo.last() to cacheInfo.dropLast(1)
|
||||
|
||||
override fun fetchCompiledJsCodeForNullCacheInfo() =
|
||||
error("Should never happen for per module granularity")
|
||||
|
||||
override fun fetchCompiledJsCode(cacheInfo: CachedModuleInfo) = cacheInfo.artifact.artifactsDir?.let { cacheDir ->
|
||||
val jsCodeFile = File(cacheDir, CACHED_MODULE_JS).ifExists { this }
|
||||
val sourceMapFile = File(cacheDir, CACHED_MODULE_JS_MAP).ifExists { this }
|
||||
val tsDefinitionsFile = File(cacheDir, CACHED_MODULE_D_TS).ifExists { this }
|
||||
jsCodeFile?.let { CompilationOutputsCached(it, sourceMapFile, tsDefinitionsFile) }
|
||||
}
|
||||
|
||||
fun commitCompiledJsCode(artifact: ModuleArtifact, compilationOutputs: CompilationOutputsBuilt): CompilationOutputs =
|
||||
artifact.artifactsDir?.let { cacheDir ->
|
||||
override fun commitCompiledJsCode(cacheInfo: CachedModuleInfo, compilationOutputs: CompilationOutputsBuilt): CompilationOutputs =
|
||||
cacheInfo.artifact.artifactsDir?.let { cacheDir ->
|
||||
val jsCodeFile = File(cacheDir, CACHED_MODULE_JS)
|
||||
val jsMapFile = File(cacheDir, CACHED_MODULE_JS_MAP)
|
||||
File(cacheDir, CACHED_MODULE_D_TS).writeIfNotNull(compilationOutputs.tsDefinitions?.raw)
|
||||
compilationOutputs.writeJsCodeIntoModuleCache(jsCodeFile, jsMapFile)
|
||||
} ?: compilationOutputs
|
||||
|
||||
fun loadProgramHeadersFromCache(): List<CachedModuleInfo> {
|
||||
override fun loadProgramHeadersFromCache(): List<CachedModuleInfo> {
|
||||
return moduleArtifacts.map { artifact ->
|
||||
fun loadModuleInfo() = CachedModuleInfo(artifact, artifact.loadJsIrModule().makeModuleHeader())
|
||||
val actualInfo = when {
|
||||
@@ -130,7 +88,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadRequiredJsIrModules(crossModuleReferences: Map<JsIrModuleHeader, CrossModuleReferences>) {
|
||||
override fun loadRequiredJsIrModules(crossModuleReferences: Map<JsIrModuleHeader, CrossModuleReferences>) {
|
||||
for ((header, references) in crossModuleReferences) {
|
||||
val cachedInfo = headerToCachedInfo[header] ?: notFoundIcError("artifact for module ${header.moduleName}")
|
||||
val actualCrossModuleHash = references.crossModuleReferencesHashForIC()
|
||||
+50
-11
@@ -29,13 +29,16 @@ abstract class CompilationOutputs {
|
||||
|
||||
abstract fun writeJsCode(outputJsFile: File, outputJsMapFile: File)
|
||||
|
||||
fun writeAll(outputDir: File, outputName: String, genDTS: Boolean, moduleName: String, moduleKind: ModuleKind): Collection<File> {
|
||||
val writtenFiles = LinkedHashSet<File>(2 * (dependencies.size + 1) + 1)
|
||||
fun createWrittenFilesContainer(): MutableSet<File> = LinkedHashSet(2 * (dependencies.size + 1) + 1)
|
||||
|
||||
open fun writeAll(outputDir: File, outputName: String, genDTS: Boolean, moduleName: String, moduleKind: ModuleKind): Collection<File> {
|
||||
val writtenFiles = createWrittenFilesContainer()
|
||||
|
||||
fun File.writeAsJsFile(out: CompilationOutputs) {
|
||||
parentFile.mkdirs()
|
||||
val jsMapFile = mapForJsFile
|
||||
val jsFile = normalizedAbsoluteFile
|
||||
|
||||
out.writeJsCode(jsFile, jsMapFile)
|
||||
|
||||
writtenFiles += jsFile
|
||||
@@ -55,9 +58,11 @@ abstract class CompilationOutputs {
|
||||
writtenFiles += dtsFile
|
||||
}
|
||||
|
||||
Files.walk(outputDir.toPath()).map { it.toFile() }.filter { it != outputDir && it !in writtenFiles }.forEach(File::delete)
|
||||
return writtenFiles.also { deleteNonWrittenFiles(outputDir, it) }
|
||||
}
|
||||
|
||||
return writtenFiles
|
||||
fun deleteNonWrittenFiles(outputDir: File, writtenFiles: Set<File>) {
|
||||
Files.walk(outputDir.toPath()).map { it.toFile() }.filter { it != outputDir && it !in writtenFiles }.forEach(File::delete)
|
||||
}
|
||||
|
||||
fun getFullTsDefinition(moduleName: String, moduleKind: ModuleKind): String {
|
||||
@@ -65,13 +70,13 @@ abstract class CompilationOutputs {
|
||||
return allTsDefinitions.toTypeScript(moduleName, moduleKind)
|
||||
}
|
||||
|
||||
private val File.normalizedAbsoluteFile
|
||||
protected val File.normalizedAbsoluteFile
|
||||
get() = absoluteFile.normalize()
|
||||
|
||||
private val File.mapForJsFile
|
||||
protected val File.mapForJsFile
|
||||
get() = resolveSibling("$name.map").normalizedAbsoluteFile
|
||||
|
||||
private val File.dtsForJsFile
|
||||
protected val File.dtsForJsFile
|
||||
get() = resolveSibling("$nameWithoutExtension.d.ts").normalizedAbsoluteFile
|
||||
}
|
||||
|
||||
@@ -100,13 +105,47 @@ class CompilationOutputsBuilt(
|
||||
outputJsFile.writeText(rawJsCode + sourceMappingUrl)
|
||||
}
|
||||
|
||||
fun writeJsCodeIntoModuleCache(outputJsFile: File, outputJsMapFile: File): CompilationOutputsBuiltForCache {
|
||||
sourceMap?.let { outputJsMapFile.writeText(it) }
|
||||
fun writeJsCodeIntoModuleCache(outputJsFile: File, outputJsMapFile: File?): CompilationOutputsBuiltForCache {
|
||||
sourceMap?.let { outputJsMapFile?.writeText(it) }
|
||||
outputJsFile.writeText(rawJsCode)
|
||||
return CompilationOutputsBuiltForCache(outputJsFile, outputJsMapFile, this)
|
||||
}
|
||||
}
|
||||
|
||||
// The output emulates the main module that has all the dependencies. In per-module we expect that the last processed module is a main module
|
||||
// and after the compilation we rename it with the provided [outputName] and save all of its dependencies, but with the per-file mode we don't have
|
||||
// this last "main" module, as a result we need to emulate it with the output. Also, it helps to save .d.ts files file-by-file instead of the generating
|
||||
// one big main .d.ts file
|
||||
class PerFileEntryPointCompilationOutput : CompilationOutputs() {
|
||||
override val tsDefinitions: TypeScriptFragment? = null
|
||||
override val jsProgram: JsProgram? = null
|
||||
|
||||
override fun writeJsCode(outputJsFile: File, outputJsMapFile: File) {}
|
||||
|
||||
override fun writeAll(outputDir: File, outputName: String, genDTS: Boolean, moduleName: String, moduleKind: ModuleKind): Collection<File> {
|
||||
val writtenFiles = createWrittenFilesContainer()
|
||||
|
||||
dependencies.forEach { (name, content) ->
|
||||
val dependencyFile = outputDir.resolve("$name${moduleKind.extension}").also { it.parentFile.mkdirs() }
|
||||
val jsMapFile = dependencyFile.mapForJsFile
|
||||
val jsFile = dependencyFile.normalizedAbsoluteFile
|
||||
val tsFile = jsFile.dtsForJsFile
|
||||
|
||||
content.writeJsCode(jsFile, jsMapFile)
|
||||
|
||||
writtenFiles += jsFile
|
||||
writtenFiles += jsMapFile
|
||||
|
||||
content.tsDefinitions.takeIf { genDTS }?.let {
|
||||
tsFile.writeText(listOf(it).toTypeScript(name, moduleKind))
|
||||
writtenFiles += tsFile
|
||||
}
|
||||
}
|
||||
|
||||
return writtenFiles.also { deleteNonWrittenFiles(outputDir, it) }
|
||||
}
|
||||
}
|
||||
|
||||
class CompilationOutputsCached(
|
||||
private val jsCodeFile: File,
|
||||
private val sourceMapFile: File?,
|
||||
@@ -142,7 +181,7 @@ class CompilationOutputsCached(
|
||||
|
||||
class CompilationOutputsBuiltForCache(
|
||||
private val jsCodeFile: File,
|
||||
private val sourceMapFile: File,
|
||||
private val sourceMapFile: File?,
|
||||
private val outputBuilt: CompilationOutputsBuilt
|
||||
) : CompilationOutputs() {
|
||||
|
||||
@@ -160,6 +199,6 @@ class CompilationOutputsBuiltForCache(
|
||||
outputBuilt.writeJsCode(outputJsFile, outputJsMapFile)
|
||||
|
||||
jsCodeFile.copyModificationTimeFrom(outputJsFile)
|
||||
sourceMapFile.copyModificationTimeFrom(outputJsMapFile)
|
||||
sourceMapFile?.copyModificationTimeFrom(outputJsMapFile)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-26
@@ -264,12 +264,12 @@ class IrModuleToJsTransformer(
|
||||
|
||||
for (fileExports in module.files) {
|
||||
if (fileExports.file.couldBeSkipped()) continue
|
||||
val (programFragment, exportProgramFragment) = generateProgramFragment(fileExports, mode)
|
||||
val programFragments = generateProgramFragment(fileExports, mode)
|
||||
|
||||
add(fileExports.toJsIrModule(mode, programFragment))
|
||||
add(fileExports.toJsIrModule(programFragments.mainFragment))
|
||||
|
||||
if (fileExports.exports.isNotEmpty()) {
|
||||
add(fileExports.toJsIrModuleForExport(module, mode, exportProgramFragment))
|
||||
programFragments.exportFragment?.let {
|
||||
add(fileExports.toJsIrModuleForExport(module, it))
|
||||
hasFileWithJsExportedDeclaration = true
|
||||
}
|
||||
}
|
||||
@@ -283,22 +283,18 @@ class IrModuleToJsTransformer(
|
||||
return JsIrProgram(modulesPerFile)
|
||||
}
|
||||
|
||||
private fun IrFileExports.toJsIrModule(mode: TranslationMode, programFragment: JsIrProgramFragment): JsIrModule {
|
||||
private fun IrFileExports.toJsIrModule(programFragment: JsIrProgramFragment): JsIrModule {
|
||||
return JsIrModule(
|
||||
moduleFragmentToNameMapper.getSafeNameFor(file),
|
||||
moduleFragmentToNameMapper.getExternalNameFor(file, mode.granularity),
|
||||
moduleFragmentToNameMapper.getExternalNameFor(file),
|
||||
listOf(programFragment),
|
||||
)
|
||||
}
|
||||
|
||||
private fun IrFileExports.toJsIrModuleForExport(
|
||||
module: IrAndExportedDeclarations,
|
||||
mode: TranslationMode,
|
||||
programFragment: JsIrProgramFragment
|
||||
): JsIrModule {
|
||||
private fun IrFileExports.toJsIrModuleForExport(module: IrAndExportedDeclarations, programFragment: JsIrProgramFragment): JsIrModule {
|
||||
return JsIrModule(
|
||||
moduleFragmentToNameMapper.getSafeNameExporterFor(file),
|
||||
moduleFragmentToNameMapper.getExternalNameForExporterFile(file, mode.granularity),
|
||||
moduleFragmentToNameMapper.getExternalNameForExporterFile(file),
|
||||
listOf(programFragment),
|
||||
module.fragment.safeName
|
||||
)
|
||||
@@ -308,7 +304,7 @@ class IrModuleToJsTransformer(
|
||||
return JsIrModule(
|
||||
fragment.safeName,
|
||||
moduleFragmentToNameMapper.getExternalNameFor(fragment),
|
||||
listOf(JsIrProgramFragment("<proxy-file>"))
|
||||
listOf(JsIrProgramFragment(fragment.safeName, "<proxy-file>"))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -319,22 +315,21 @@ class IrModuleToJsTransformer(
|
||||
private fun IrFileExports.generateProgramFragmentForExport(
|
||||
mode: TranslationMode,
|
||||
nameScope: NameTable<IrDeclaration>
|
||||
): JsIrProgramFragment {
|
||||
): JsIrProgramFragment? {
|
||||
if (exports.isEmpty()) return null
|
||||
|
||||
val globalNames = NameTable<String>(nameScope)
|
||||
val nameGenerator = JsNameLinkingNamer(backendContext, mode.minimizedMemberNames, isEsModules)
|
||||
val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
|
||||
val staticContext = JsStaticContext(backendContext, nameGenerator, nameScope, mode)
|
||||
|
||||
return JsIrProgramFragment(file.packageFqName.asString()).apply {
|
||||
dts = tsDeclarations
|
||||
exports.statements += ExportModelToJsStatements(staticContext, backendContext.es6mode, { globalNames.declareFreshName(it, it) })
|
||||
.generateModuleExport(
|
||||
ExportedModule(mainModuleName, moduleKind, this@generateProgramFragmentForExport.exports),
|
||||
internalModuleName,
|
||||
isEsModules
|
||||
)
|
||||
computeAndSaveNameBindings(emptySet(), nameGenerator)
|
||||
}
|
||||
return JsIrProgramFragment("", file.packageFqName.asString())
|
||||
.also {
|
||||
it.dts = tsDeclarations
|
||||
it.exports.statements += ExportModelToJsStatements(staticContext, backendContext.es6mode, { globalNames.declareFreshName(it, it) })
|
||||
.generateModuleExport(ExportedModule(mainModuleName, moduleKind, exports), internalModuleName, isEsModules)
|
||||
it.computeAndSaveNameBindings(emptySet(), nameGenerator)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateProgramFragment(fileExports: IrFileExports, mode: TranslationMode): List<JsIrProgramFragment> {
|
||||
@@ -342,7 +337,10 @@ class IrModuleToJsTransformer(
|
||||
val nameGenerator = JsNameLinkingNamer(backendContext, mode.minimizedMemberNames, isEsModules)
|
||||
val staticContext = JsStaticContext(backendContext, nameGenerator, globalNameScope, mode)
|
||||
|
||||
val result = JsIrProgramFragment(fileExports.file.packageFqName.asString()).apply {
|
||||
val result = JsIrProgramFragment(
|
||||
fileExports.file.getJsName() ?: fileExports.file.nameWithoutExtension,
|
||||
fileExports.file.packageFqName.asString()
|
||||
).apply {
|
||||
if (shouldGeneratePolyfills) {
|
||||
polyfills.statements += backendContext.polyfills.getAllPolyfillsFor(fileExports.file)
|
||||
}
|
||||
@@ -415,7 +413,7 @@ class IrModuleToJsTransformer(
|
||||
optimizeFragmentByJsAst(result)
|
||||
}
|
||||
|
||||
return listOf(result, exportFragment)
|
||||
return listOfNotNull(result, exportFragment)
|
||||
}
|
||||
|
||||
private fun Set<IrDeclaration>.computeTag(declaration: IrDeclaration): String? {
|
||||
|
||||
+14
-3
@@ -5,13 +5,14 @@
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64String
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.toJsIdentifier
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import java.io.File
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
|
||||
class JsIrProgramFragment(val packageFqn: String) {
|
||||
class JsIrProgramFragment(val name: String, val packageFqn: String) {
|
||||
val nameBindings = mutableMapOf<String, JsName>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
val declarations = JsCompositeBlock()
|
||||
@@ -150,7 +151,14 @@ private class JsIrModuleCrossModuleReferenceBuilder(
|
||||
|
||||
fun buildExportNames(startIndex: Int = 0) {
|
||||
var index = startIndex
|
||||
exportNames = exports.sorted().associateWith { index++.toJsIdentifier() }
|
||||
exportNames = exports.sorted().associateWith { tag ->
|
||||
// Bundlers should minimize the names by ourselves. Ex, webpack has `optimization.mangleExports` property
|
||||
if (moduleKind == ModuleKind.ES) {
|
||||
"${header.nameBindings[tag]}${tag.cityHash64String()}"
|
||||
} else {
|
||||
index++.toJsIdentifier()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun buildCrossModuleRefs(): CrossModuleReferences {
|
||||
@@ -267,4 +275,7 @@ fun JsStatement.renameImportedSymbolInternalName(newName: JsName): JsStatement {
|
||||
is JsVars -> JsVars(JsVars.JsVar(newName, vars.single().initExpression))
|
||||
else -> error("Unexpected cross-module import statement ${this::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val List<JsIrProgramFragment>.mainFragment: JsIrProgramFragment get() = first()
|
||||
val List<JsIrProgramFragment>.exportFragment: JsIrProgramFragment? get() = getOrNull(1)
|
||||
+1
-1
@@ -41,7 +41,7 @@ class Merger(
|
||||
error("Missing name for declaration '${declaration}'")
|
||||
}
|
||||
|
||||
importStatements.putIfAbsent(declaration, rename(importStatement).importStatementWithName(importName))
|
||||
importStatements.putIfAbsent(declaration, rename(importStatement.importStatementWithName(importName)))
|
||||
}
|
||||
|
||||
val classModels = (mutableMapOf<JsName, JsIrIcClassModel>() + f.classes)
|
||||
|
||||
+19
-13
@@ -6,10 +6,10 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.nameWithoutExtension
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.name
|
||||
import org.jetbrains.kotlin.ir.declarations.path
|
||||
|
||||
private const val EXPORTER_FILE_POSTFIX = ".export"
|
||||
@@ -17,9 +17,8 @@ private const val EXPORTER_FILE_POSTFIX = ".export"
|
||||
class ModuleFragmentToExternalName(private val jsOutputNamesMapping: Map<IrModuleFragment, String>) {
|
||||
private val externalNameToItsFile = hashMapOf<String, IrFile>()
|
||||
|
||||
fun getExternalNameFor(file: IrFile, granularity: JsGenerationGranularity): String {
|
||||
assert(granularity == JsGenerationGranularity.PER_FILE) { "This method should be used only for PER_FILE granularity" }
|
||||
return file.module.getJsOutputName().getExternalModuleNameForPerFile(file).also {
|
||||
fun getExternalNameFor(file: IrFile): String {
|
||||
return getExternalNameFor(file.outputName, file.packageFqName.asString(), file.module.getJsOutputName()).also {
|
||||
val alreadyReservedBy = externalNameToItsFile.putIfAbsent(it.lowercase(), file)
|
||||
|
||||
if (alreadyReservedBy != null && alreadyReservedBy != file) {
|
||||
@@ -35,8 +34,16 @@ class ModuleFragmentToExternalName(private val jsOutputNamesMapping: Map<IrModul
|
||||
}
|
||||
}
|
||||
|
||||
fun getExternalNameForExporterFile(file: IrFile, granularity: JsGenerationGranularity): String {
|
||||
return "${getExternalNameFor(file, granularity)}$EXPORTER_FILE_POSTFIX"
|
||||
fun getExternalNameFor(fileName: String, packageFqn: String, moduleName: String): String {
|
||||
return "$moduleName/${getFileStableName(fileName, packageFqn)}"
|
||||
}
|
||||
|
||||
fun getExternalNameForExporterFile(file: IrFile): String {
|
||||
return getExternalNameForExporterFile(file.outputName, file.packageFqName.asString(), file.module.getJsOutputName())
|
||||
}
|
||||
|
||||
fun getExternalNameForExporterFile(fileName: String, packageFqn: String, moduleName: String): String {
|
||||
return "${getExternalNameFor(fileName, packageFqn, moduleName)}$EXPORTER_FILE_POSTFIX"
|
||||
}
|
||||
|
||||
fun getSafeNameFor(file: IrFile): String {
|
||||
@@ -55,12 +62,11 @@ class ModuleFragmentToExternalName(private val jsOutputNamesMapping: Map<IrModul
|
||||
return jsOutputNamesMapping[this] ?: sanitizeName(safeName)
|
||||
}
|
||||
|
||||
private fun String.getExternalModuleNameForPerFile(file: IrFile) = "$this/${file.stableFileName}"
|
||||
private fun getFileStableName(fileName: String, packageFqn: String): String {
|
||||
val prefix = packageFqn.replace('.', '/')
|
||||
return "$prefix${if (prefix.isNotEmpty()) "/" else ""}$fileName"
|
||||
}
|
||||
|
||||
private val IrFile.stableFileName: String
|
||||
get() {
|
||||
val prefix = packageFqName.asString().replace('.', '/')
|
||||
val fileName = getJsName() ?: name.substringBefore(".kt")
|
||||
return "$prefix${if (prefix.isNotEmpty()) "/" else ""}$fileName"
|
||||
}
|
||||
private val IrFile.outputName: String get() = getJsName() ?: nameWithoutExtension
|
||||
private val IrFile.stableFileName: String get() = getFileStableName(outputName, packageFqName.asString())
|
||||
}
|
||||
@@ -35,6 +35,8 @@ import org.jetbrains.kotlin.js.backend.ast.JsNameRef
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.utils.filterIsInstanceAnd
|
||||
|
||||
val IrFile.nameWithoutExtension: String get() = name.substringBeforeLast(".kt")
|
||||
|
||||
fun IrClass.jsConstructorReference(context: JsIrBackendContext): IrExpression {
|
||||
return JsIrBuilder.buildCall(context.intrinsics.jsClass, origin = JsStatementOrigins.CLASS_REFERENCE)
|
||||
.apply { putTypeArgument(0, defaultType) }
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
|
||||
}
|
||||
|
||||
fun readFragment(): JsIrProgramFragment {
|
||||
return JsIrProgramFragment(readString()).apply {
|
||||
return JsIrProgramFragment(readString(), readString()).apply {
|
||||
readRepeated {
|
||||
importedModules += JsImportedModule(
|
||||
externalName = stringTable[readInt()],
|
||||
|
||||
+1
@@ -114,6 +114,7 @@ private class JsIrAstSerializer {
|
||||
}
|
||||
|
||||
private fun DataWriter.writeFragment(fragment: JsIrProgramFragment) {
|
||||
writeString(fragment.name)
|
||||
writeString(fragment.packageFqn)
|
||||
|
||||
writeCollection(fragment.importedModules) {
|
||||
|
||||
+3
@@ -216,6 +216,9 @@ public fun cityHash64(s: ByteArray, pos: Int = 0, len: Int = s.size): ULong {
|
||||
fun String.cityHash64(): Long =
|
||||
cityHash64(this.toByteArray()).toLong()
|
||||
|
||||
fun String.cityHash64String(): String =
|
||||
cityHash64(this.toByteArray()).toString(Character.MAX_RADIX)
|
||||
|
||||
data class Hash128Bits(val lowBytes: ULong = k0, val highBytes: ULong = k1) {
|
||||
private infix fun ULong.combineHash(other: ULong) = other xor (this + kGoldenRatio + (other shl 12) + (other shr 4))
|
||||
|
||||
|
||||
@@ -6,13 +6,27 @@
|
||||
package org.jetbrains.kotlin.codegen
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.DirtyFileState
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ProjectInfo(val name: String, val modules: List<String>, val steps: List<ProjectBuildStep>, val muted: Boolean, val moduleKind: ModuleKind) {
|
||||
class ProjectInfo(
|
||||
val name: String,
|
||||
val modules: List<String>,
|
||||
val steps: List<ProjectBuildStep>,
|
||||
val muted: Boolean,
|
||||
val moduleKind: ModuleKind,
|
||||
val ignoredGranularities: Set<JsGenerationGranularity>
|
||||
) {
|
||||
|
||||
class ProjectBuildStep(val id: Int, val order: List<String>, val dirtyJS: List<String>, val language: List<String>)
|
||||
class ProjectBuildStep(
|
||||
val id: Int,
|
||||
val order: List<String>,
|
||||
val dirtyJsFiles: List<String>,
|
||||
val dirtyJsModules: List<String>,
|
||||
val language: List<String>,
|
||||
)
|
||||
}
|
||||
|
||||
class ModuleInfo(val moduleName: String) {
|
||||
@@ -60,8 +74,11 @@ const val PROJECT_INFO_FILE = "project.info"
|
||||
private const val MODULES_LIST = "MODULES"
|
||||
private const val MODULES_KIND = "MODULE_KIND"
|
||||
private const val LIBS_LIST = "libs"
|
||||
private const val DIRTY_JS_MODULES_LIST = "dirty js"
|
||||
private const val DIRTY_JS_FILES_LIST = "dirty js files"
|
||||
private const val DIRTY_JS_MODULES_LIST = "dirty js modules"
|
||||
private const val LANGUAGE = "language"
|
||||
private const val IGNORE_PER_FILE = "IGNORE_PER_FILE"
|
||||
private const val IGNORE_PER_MODULE = "IGNORE_PER_MODULE"
|
||||
|
||||
const val MODULE_INFO_FILE = "module.info"
|
||||
private const val DEPENDENCIES = "dependencies"
|
||||
@@ -119,7 +136,8 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
private fun parseSteps(firstId: Int, lastId: Int): List<ProjectInfo.ProjectBuildStep> {
|
||||
val order = mutableListOf<String>()
|
||||
val dirtyJS = mutableListOf<String>()
|
||||
val dirtyJsFiles = mutableListOf<String>()
|
||||
val dirtyJsModules = mutableListOf<String>()
|
||||
val language = mutableListOf<String>()
|
||||
|
||||
loop { line ->
|
||||
@@ -138,7 +156,8 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
when (op) {
|
||||
LIBS_LIST -> order += split[1].splitAndTrim()
|
||||
DIRTY_JS_MODULES_LIST -> dirtyJS += split[1].splitAndTrim()
|
||||
DIRTY_JS_FILES_LIST -> dirtyJsFiles += split[1].splitAndTrim()
|
||||
DIRTY_JS_MODULES_LIST -> dirtyJsModules += split[1].splitAndTrim()
|
||||
LANGUAGE -> language += split[1].splitAndTrim()
|
||||
else -> println(diagnosticMessage("Unknown op $op", line))
|
||||
}
|
||||
@@ -146,12 +165,13 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
false
|
||||
}
|
||||
|
||||
return (firstId..lastId).map { ProjectInfo.ProjectBuildStep(it, order, dirtyJS, language) }
|
||||
return (firstId..lastId).map { ProjectInfo.ProjectBuildStep(it, order, dirtyJsFiles, dirtyJsModules, language) }
|
||||
}
|
||||
|
||||
override fun parse(entryName: String): ProjectInfo {
|
||||
val libraries = mutableListOf<String>()
|
||||
val steps = mutableListOf<ProjectInfo.ProjectBuildStep>()
|
||||
val ignoredGranularities = mutableSetOf<JsGenerationGranularity>()
|
||||
var muted = false
|
||||
var moduleKind = ModuleKind.ES
|
||||
|
||||
@@ -171,6 +191,8 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
when {
|
||||
op == MODULES_LIST -> libraries += split[1].splitAndTrim()
|
||||
op == IGNORE_PER_FILE && split[1].trim() == "true" -> ignoredGranularities += JsGenerationGranularity.PER_FILE
|
||||
op == IGNORE_PER_MODULE && split[1].trim() == "true" -> ignoredGranularities += JsGenerationGranularity.PER_MODULE
|
||||
op == MODULES_KIND -> moduleKind = split[1].trim()
|
||||
.ifEmpty { error("Module kind value should be provided if MODULE_KIND pragma was specified") }
|
||||
.let { moduleKindMap[it] ?: error("Unknown MODULE_KIND value '$it'") }
|
||||
@@ -200,7 +222,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
false
|
||||
}
|
||||
|
||||
return ProjectInfo(entryName, libraries, steps, muted, moduleKind)
|
||||
return ProjectInfo(entryName, libraries, steps, muted, moduleKind, ignoredGranularities)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user