[JS IR] IC improvements
- Reuse a built fragment - Use cache for saving built JS code
This commit is contained in:
committed by
Space
parent
f8ae097e50
commit
9952d43252
@@ -286,23 +286,22 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
messageCollector.report(INFO, arguments.cacheDirectories ?: "")
|
||||
|
||||
if (icCaches.isNotEmpty()) {
|
||||
|
||||
val beforeIc2Js = System.currentTimeMillis()
|
||||
|
||||
val moduleKind = configurationJs[JSConfigurationKeys.MODULE_KIND]!!
|
||||
|
||||
val translationMode = TranslationMode.fromFlags(false, arguments.irPerModule, false)
|
||||
|
||||
val compiledModule = generateJsFromAst(
|
||||
moduleName,
|
||||
moduleKind,
|
||||
SourceMapsInfo.from(configurationJs),
|
||||
setOf(translationMode),
|
||||
icCaches,
|
||||
val jsExecutableProducer = JsExecutableProducer(
|
||||
mainModuleName = moduleName,
|
||||
moduleKind = configurationJs[JSConfigurationKeys.MODULE_KIND]!!,
|
||||
sourceMapsInfo = SourceMapsInfo.from(configurationJs),
|
||||
caches = icCaches,
|
||||
relativeRequirePath = true
|
||||
)
|
||||
|
||||
val outputs = compiledModule.outputs.values.single()
|
||||
val outputs = jsExecutableProducer.buildExecutable(
|
||||
multiModule = arguments.irPerModule,
|
||||
rebuildCallback = { rebuiltModule ->
|
||||
messageCollector.report(INFO, "IC module builder rebuilt module [${File(rebuiltModule).name}]")
|
||||
}
|
||||
)
|
||||
|
||||
outputFile.write(outputs)
|
||||
outputs.dependencies.forEach { (name, content) ->
|
||||
|
||||
@@ -10,14 +10,13 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaserState
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.ArtifactCache
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.KLibArtifact
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.JsMultiModuleCache
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.ModuleArtifact
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.collectNativeImplementations
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.generateJsTests
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltInsOverDescriptors
|
||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||
@@ -25,7 +24,6 @@ import org.jetbrains.kotlin.ir.util.noUnboundLeft
|
||||
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
||||
@@ -99,9 +97,11 @@ fun compileWithIC(
|
||||
module.files.filter { it.fileEntry.name in dirties }
|
||||
} ?: module.files
|
||||
|
||||
val ast = transformer.generateBinaryAst(dirtyFiles, allModules)
|
||||
|
||||
ast.entries.forEach { (path, bytes) -> artifactCache.saveBinaryAst(path, bytes) }
|
||||
val astAndFragments = transformer.generateBinaryAst(dirtyFiles, allModules)
|
||||
astAndFragments.forEach {
|
||||
artifactCache.saveFragment(it.srcPath, it.fragment)
|
||||
artifactCache.saveBinaryAst(it.srcPath, it.binaryAst)
|
||||
}
|
||||
}
|
||||
|
||||
fun lowerPreservingTags(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext, phaseConfig: PhaseConfig, controller: WholeWorldStageController) {
|
||||
@@ -117,38 +117,3 @@ fun lowerPreservingTags(modules: Iterable<IrModuleFragment>, context: JsIrBacken
|
||||
|
||||
controller.currentStage = pirLowerings.size + 1
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun generateJsFromAst(
|
||||
mainModuleName: String,
|
||||
moduleKind: ModuleKind,
|
||||
sourceMapsInfo: SourceMapsInfo?,
|
||||
translationModes: Set<TranslationMode>,
|
||||
caches: List<KLibArtifact>,
|
||||
relativeRequirePath: Boolean = false,
|
||||
): CompilerResult {
|
||||
fun compilationOutput(multiModule: Boolean): CompilationOutputs {
|
||||
val deserializer = JsIrAstDeserializer()
|
||||
val jsIrProgram = JsIrProgram(caches.map { cacheArtifact ->
|
||||
JsIrModule(
|
||||
cacheArtifact.moduleName.safeModuleName,
|
||||
sanitizeName(cacheArtifact.moduleName.safeModuleName),
|
||||
cacheArtifact.fileArtifacts.sortedBy { it.srcFilePath }.mapNotNull { srcFileArtifact ->
|
||||
srcFileArtifact.astFileArtifact.fetchBinaryAst()?.let { deserializer.deserialize(ByteArrayInputStream(it)) }
|
||||
})
|
||||
})
|
||||
|
||||
return generateWrappedModuleBody(
|
||||
multiModule = multiModule,
|
||||
mainModuleName = mainModuleName,
|
||||
moduleKind = moduleKind,
|
||||
jsIrProgram,
|
||||
sourceMapsInfo = sourceMapsInfo,
|
||||
relativeRequirePath = relativeRequirePath,
|
||||
generateScriptModule = false,
|
||||
)
|
||||
}
|
||||
|
||||
return CompilerResult(translationModes.associate { it to compilationOutput(it.perModule) }, null)
|
||||
}
|
||||
|
||||
@@ -5,29 +5,52 @@
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModule
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
|
||||
class SrcFileArtifact(val srcFilePath: String, astArtifactFilePath: String, astBinaryData: ByteArray?) {
|
||||
class Artifact(private val artifactFilePath: String, private var binaryData: ByteArray?) {
|
||||
fun fetchBinaryAst(): ByteArray? {
|
||||
if (binaryData == null) {
|
||||
binaryData = File(artifactFilePath).ifExists { readBytes() }
|
||||
}
|
||||
return binaryData
|
||||
class SrcFileArtifact(val srcFilePath: String, private val fragment: JsIrProgramFragment?, private val astArtifact: File? = null) {
|
||||
fun loadJsIrFragment(deserializer: JsIrAstDeserializer): JsIrProgramFragment? {
|
||||
if (fragment != null) {
|
||||
return fragment
|
||||
}
|
||||
return astArtifact?.ifExists { readBytes() }?.let {
|
||||
deserializer.deserialize(ByteArrayInputStream(it))
|
||||
}
|
||||
}
|
||||
|
||||
val astFileArtifact = Artifact(astArtifactFilePath, astBinaryData)
|
||||
fun isModified() = fragment != null
|
||||
}
|
||||
|
||||
class KLibArtifact(val moduleName: String, val fileArtifacts: List<SrcFileArtifact>)
|
||||
class ModuleArtifact(
|
||||
moduleName: String,
|
||||
val fileArtifacts: List<SrcFileArtifact>,
|
||||
val artifactsDir: File? = null,
|
||||
val forceRebuildJs: Boolean = false
|
||||
) {
|
||||
val moduleSafeName = moduleName.safeModuleName
|
||||
|
||||
fun loadJsIrModule(): JsIrModule {
|
||||
val deserializer = JsIrAstDeserializer()
|
||||
val fragments = fileArtifacts.sortedBy { it.srcFilePath }.mapNotNull { it.loadJsIrFragment(deserializer) }
|
||||
return JsIrModule(moduleSafeName, moduleSafeName, fragments)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ArtifactCache {
|
||||
protected val binaryAsts = mutableMapOf<String, ByteArray>()
|
||||
protected val fragments = mutableMapOf<String, JsIrProgramFragment>()
|
||||
|
||||
fun saveBinaryAst(srcPath: String, astData: ByteArray) {
|
||||
binaryAsts[srcPath] = astData
|
||||
fun saveBinaryAst(srcPath: String, binaryAst: ByteArray) {
|
||||
binaryAsts[srcPath] = binaryAst
|
||||
}
|
||||
|
||||
abstract fun fetchArtifacts(): KLibArtifact
|
||||
fun saveFragment(srcPath: String, fragment: JsIrProgramFragment) {
|
||||
fragments[srcPath] = fragment
|
||||
}
|
||||
|
||||
abstract fun fetchArtifacts(): ModuleArtifact
|
||||
}
|
||||
|
||||
+21
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CrossModuleReferences
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.util.DumpIrTreeVisitor
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
@@ -99,3 +100,23 @@ internal fun KotlinLibrary.fingerprint(fileIndex: Int) = HashCalculatorForIC().a
|
||||
update(declarations(fileIndex))
|
||||
update(bodies(fileIndex))
|
||||
}.finalize()
|
||||
|
||||
internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalculatorForIC().apply {
|
||||
for (importedModule in importedModules) {
|
||||
update(importedModule.externalName)
|
||||
update(importedModule.internalName.toString())
|
||||
}
|
||||
for (exportFrom in transitiveJsExportFrom) {
|
||||
update(exportFrom.toString())
|
||||
}
|
||||
for (tag in exports.keys.sorted()) {
|
||||
update(tag)
|
||||
update(exports[tag]!!)
|
||||
}
|
||||
for (tag in imports.keys.sorted()) {
|
||||
val import = imports[tag]!!
|
||||
update(tag)
|
||||
update(import.exportedAs)
|
||||
update(import.moduleExporter.toString())
|
||||
}
|
||||
}.finalize()
|
||||
|
||||
+11
-3
@@ -36,6 +36,7 @@ class IncrementalCache(private val library: KotlinLibrary, cachePath: String) :
|
||||
private enum class CacheState { NON_LOADED, FETCHED_FOR_DEPENDENCY, FETCHED_FULL }
|
||||
|
||||
private var state = CacheState.NON_LOADED
|
||||
private var forceRebuildJs = false
|
||||
|
||||
private val cacheDir = File(cachePath)
|
||||
private val signatureToIdMapping = mutableMapOf<String, Map<IdSignature, Int>>()
|
||||
@@ -232,10 +233,12 @@ class IncrementalCache(private val library: KotlinLibrary, cachePath: String) :
|
||||
|
||||
private fun clearCacheAfterCommit() {
|
||||
state = CacheState.FETCHED_FOR_DEPENDENCY
|
||||
forceRebuildJs = deletedSrcFiles.isNotEmpty()
|
||||
signatureToIdMapping.clear()
|
||||
usedInlineFunctions.clear()
|
||||
srcFilesInOrderFromKLib = emptyList()
|
||||
deletedSrcFiles = emptySet()
|
||||
binaryAsts.clear()
|
||||
}
|
||||
|
||||
fun commitCacheForRemovedSrcFiles() {
|
||||
@@ -255,11 +258,14 @@ class IncrementalCache(private val library: KotlinLibrary, cachePath: String) :
|
||||
clearCacheAfterCommit()
|
||||
}
|
||||
|
||||
override fun fetchArtifacts() = KLibArtifact(
|
||||
override fun fetchArtifacts() = ModuleArtifact(
|
||||
moduleName = cacheFastInfo.moduleName ?: error("Internal error: missing module name"),
|
||||
fileArtifacts = fingerprints.keys.map {
|
||||
SrcFileArtifact(it, getBinaryAstPath(it).absolutePath, binaryAsts[it])
|
||||
})
|
||||
SrcFileArtifact(it, fragments[it], getBinaryAstPath(it))
|
||||
},
|
||||
artifactsDir = cacheDir,
|
||||
forceRebuildJs = forceRebuildJs
|
||||
)
|
||||
|
||||
fun invalidate() {
|
||||
cacheDir.deleteRecursively()
|
||||
@@ -268,6 +274,7 @@ class IncrementalCache(private val library: KotlinLibrary, cachePath: String) :
|
||||
usedInlineFunctions.clear()
|
||||
fingerprints.clear()
|
||||
binaryAsts.clear()
|
||||
fragments.clear()
|
||||
cacheFastInfo = CacheFastInfo()
|
||||
srcFilesInOrderFromKLib = emptyList()
|
||||
deletedSrcFiles = emptySet()
|
||||
@@ -280,6 +287,7 @@ class IncrementalCache(private val library: KotlinLibrary, cachePath: String) :
|
||||
usedInlineFunctions.remove(srcPath)
|
||||
fingerprints.remove(srcPath)
|
||||
binaryAsts.remove(srcPath)
|
||||
fragments.remove(srcPath)
|
||||
}
|
||||
|
||||
private fun KotlinLibrary.filesAndSigReaders(): List<Pair<String, IdSignatureDeserializer>> {
|
||||
|
||||
+35
-19
@@ -7,10 +7,13 @@ package org.jetbrains.kotlin.ir.backend.js.ic
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||
import org.jetbrains.kotlin.ir.util.file
|
||||
import org.jetbrains.kotlin.ir.util.isFakeOverride
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
@@ -29,50 +32,63 @@ class InlineFunctionFlatHashBuilder : IrElementVisitorVoid {
|
||||
declaration.acceptChildren(this, null)
|
||||
}
|
||||
|
||||
private val flatHashes = mutableMapOf<IrSimpleFunction, ICHash>()
|
||||
private val flatHashes = mutableMapOf<IrFunction, ICHash>()
|
||||
|
||||
fun getFlatHashes() = flatHashes
|
||||
}
|
||||
|
||||
interface InlineFunctionHashProvider {
|
||||
fun hashForExternalFunction(declaration: IrSimpleFunction): ICHash?
|
||||
fun hashForExternalFunction(declaration: IrFunction): ICHash?
|
||||
}
|
||||
|
||||
class InlineFunctionHashBuilder(
|
||||
private val hashProvider: InlineFunctionHashProvider,
|
||||
private val flatHashes: Map<IrSimpleFunction, ICHash>
|
||||
private val flatHashes: Map<IrFunction, ICHash>
|
||||
) {
|
||||
private val inlineFunctionCallGraph: MutableMap<IrSimpleFunction, Set<IrSimpleFunction>> = mutableMapOf()
|
||||
private val inlineFunctionCallGraph: MutableMap<IrFunction, Set<IrFunction>> = mutableMapOf()
|
||||
|
||||
private inner class GraphBuilder : IrElementVisitor<Unit, MutableSet<IrSimpleFunction>> {
|
||||
private inner class GraphBuilder : IrElementVisitor<Unit, MutableSet<IrFunction>> {
|
||||
var inlineFunctionCallDepth: Int = 0
|
||||
|
||||
override fun visitElement(element: IrElement, data: MutableSet<IrSimpleFunction>) {
|
||||
override fun visitElement(element: IrElement, data: MutableSet<IrFunction>) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: MutableSet<IrSimpleFunction>) {
|
||||
val newGraph = mutableSetOf<IrSimpleFunction>()
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: MutableSet<IrFunction>) {
|
||||
val newGraph = mutableSetOf<IrFunction>()
|
||||
inlineFunctionCallGraph[declaration] = newGraph
|
||||
declaration.acceptChildren(this, newGraph)
|
||||
}
|
||||
|
||||
|
||||
override fun visitCall(expression: IrCall, data: MutableSet<IrSimpleFunction>) {
|
||||
override fun visitCall(expression: IrCall, data: MutableSet<IrFunction>) {
|
||||
val callee = expression.symbol.owner
|
||||
|
||||
if (callee.isInline) {
|
||||
data.add(callee)
|
||||
data += callee
|
||||
inlineFunctionCallDepth += 1
|
||||
}
|
||||
expression.acceptChildren(this, data)
|
||||
if (callee.isInline) {
|
||||
inlineFunctionCallDepth -= 1
|
||||
if (inlineFunctionCallDepth < 0) {
|
||||
error("Internal error: inline function calls depth inconsistency")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference, data: MutableSet<IrFunction>) {
|
||||
val reference = expression.symbol.owner
|
||||
if (inlineFunctionCallDepth > 0 && reference.isInline) {
|
||||
data += reference
|
||||
}
|
||||
expression.acceptChildren(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class InlineFunctionHashProcessor {
|
||||
private val computedHashes = mutableMapOf<IrSimpleFunction, ICHash>()
|
||||
private val processingFunctions = mutableSetOf<IrSimpleFunction>()
|
||||
private val computedHashes = mutableMapOf<IrFunction, ICHash>()
|
||||
private val processingFunctions = mutableSetOf<IrFunction>()
|
||||
|
||||
private fun processInlineFunction(f: IrSimpleFunction): ICHash = computedHashes.getOrPut(f) {
|
||||
private fun processInlineFunction(f: IrFunction): ICHash = computedHashes.getOrPut(f) {
|
||||
if (!processingFunctions.add(f)) {
|
||||
error("Inline circle through function ${f.render()} detected")
|
||||
}
|
||||
@@ -86,14 +102,14 @@ class InlineFunctionHashBuilder(
|
||||
functionInlineHash
|
||||
}
|
||||
|
||||
private fun processCallee(callee: IrSimpleFunction): ICHash {
|
||||
private fun processCallee(callee: IrFunction): ICHash {
|
||||
if (callee in flatHashes) {
|
||||
return processInlineFunction(callee)
|
||||
}
|
||||
return hashProvider.hashForExternalFunction(callee) ?: error("Internal error: No hash found for ${callee.render()}")
|
||||
}
|
||||
|
||||
fun process(): Map<IrSimpleFunction, ICHash> {
|
||||
fun process(): Map<IrFunction, ICHash> {
|
||||
for ((f, callees) in inlineFunctionCallGraph.entries) {
|
||||
if (f.isInline) {
|
||||
processInlineFunction(f)
|
||||
@@ -105,12 +121,12 @@ class InlineFunctionHashBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
fun buildHashes(dirtyFiles: Collection<IrFile>): Map<IrSimpleFunction, ICHash> {
|
||||
fun buildHashes(dirtyFiles: Collection<IrFile>): Map<IrFunction, ICHash> {
|
||||
dirtyFiles.forEach { it.acceptChildren(GraphBuilder(), mutableSetOf()) }
|
||||
return InlineFunctionHashProcessor().process()
|
||||
}
|
||||
|
||||
fun buildInlineGraph(computedHashed: Map<IrSimpleFunction, ICHash>): Map<IrFile, Map<IdSignature, ICHash>> {
|
||||
fun buildInlineGraph(computedHashed: Map<IrFunction, ICHash>): Map<IrFile, Map<IdSignature, ICHash>> {
|
||||
val perFileInlineGraph = inlineFunctionCallGraph.entries.groupBy({ it.key.file }) {
|
||||
it.value
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.ir.backend.js.CompilationOutputs
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
|
||||
class JsExecutableProducer(
|
||||
private val mainModuleName: String,
|
||||
private val moduleKind: ModuleKind,
|
||||
private val sourceMapsInfo: SourceMapsInfo?,
|
||||
private val caches: List<ModuleArtifact>,
|
||||
private val relativeRequirePath: Boolean
|
||||
) {
|
||||
fun buildExecutable(multiModule: Boolean, rebuildCallback: (String) -> Unit = {}) = if (multiModule) {
|
||||
buildMultiModuleExecutable(rebuildCallback)
|
||||
} else {
|
||||
buildSingleModuleExecutable(rebuildCallback)
|
||||
}
|
||||
|
||||
private fun buildSingleModuleExecutable(rebuildCallback: (String) -> Unit): CompilationOutputs {
|
||||
val program = JsIrProgram(caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() })
|
||||
val out = generateSingleWrappedModuleBody(
|
||||
moduleName = mainModuleName,
|
||||
moduleKind = moduleKind,
|
||||
fragments = program.modules.flatMap { it.fragments },
|
||||
sourceMapsInfo = sourceMapsInfo,
|
||||
generateScriptModule = false,
|
||||
generateCallToMain = true
|
||||
)
|
||||
rebuildCallback(mainModuleName)
|
||||
return out
|
||||
}
|
||||
|
||||
private fun buildMultiModuleExecutable(rebuildCallback: (String) -> Unit): CompilationOutputs {
|
||||
val jsMultiModuleCache = JsMultiModuleCache(caches)
|
||||
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
|
||||
|
||||
val resolver = CrossModuleDependenciesResolver(cachedProgram.map { it.jsIrHeader })
|
||||
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
|
||||
|
||||
jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences)
|
||||
|
||||
fun JsMultiModuleCache.CachedModuleInfo.compileModule(moduleName: String, generateCallToMain: Boolean): CompilationOutputs {
|
||||
if (jsIrHeader.associatedModule == null) {
|
||||
val compilationOutputs = jsMultiModuleCache.fetchCompiledJsCode(artifact)
|
||||
if (compilationOutputs != null) {
|
||||
return compilationOutputs
|
||||
}
|
||||
jsIrHeader.associatedModule = artifact.loadJsIrModule()
|
||||
}
|
||||
val associatedModule = jsIrHeader.associatedModule ?: error("Internal error: cannot load module $moduleName")
|
||||
val crossRef = crossModuleReferences[jsIrHeader] ?: error("Internal error: cannot find cross references for module $moduleName")
|
||||
crossRef.initJsImportsForModule(associatedModule)
|
||||
|
||||
val compiledModule = generateSingleWrappedModuleBody(
|
||||
moduleName = moduleName,
|
||||
moduleKind = moduleKind,
|
||||
associatedModule.fragments,
|
||||
sourceMapsInfo = sourceMapsInfo,
|
||||
generateScriptModule = false,
|
||||
generateCallToMain = generateCallToMain,
|
||||
crossModuleReferences = crossRef
|
||||
)
|
||||
jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule)
|
||||
rebuildCallback(moduleName)
|
||||
return compiledModule
|
||||
}
|
||||
|
||||
val cachedMainModule = cachedProgram.last()
|
||||
val mainModule = cachedMainModule.compileModule(mainModuleName, true)
|
||||
|
||||
val cachedOtherModules = cachedProgram.dropLast(1)
|
||||
val dependencies = cachedOtherModules.map {
|
||||
it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false)
|
||||
}
|
||||
return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies)
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.ir.backend.js.CompilationOutputs
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CrossModuleReferences
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModuleHeader
|
||||
import java.io.File
|
||||
|
||||
class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
companion object {
|
||||
private const val cacheModuleHeader = "cache.module.header.info"
|
||||
private const val cacheJsModuleFile = "cache.module.js"
|
||||
private const val cacheJsMapModuleFile = "cache.module.js.map"
|
||||
}
|
||||
|
||||
private enum class NameType(val typeMask: Int) {
|
||||
DEFINITIONS(0b01), NAME_BINDINGS(0b10)
|
||||
}
|
||||
|
||||
class CachedModuleInfo(val artifact: ModuleArtifact, val jsIrHeader: JsIrModuleHeader, var crossModuleReferencesHash: ICHash = ICHash())
|
||||
|
||||
private val headerToCachedInfo = mutableMapOf<JsIrModuleHeader, CachedModuleInfo>()
|
||||
|
||||
private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, cacheModuleHeader).useCodedInputIfExists {
|
||||
val definitions = mutableSetOf<String>()
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
|
||||
val crossModuleReferencesHash = ICHash.fromProtoStream(this)
|
||||
val hasJsExports = readBool()
|
||||
repeat(readInt32()) {
|
||||
val tag = readString()
|
||||
val mask = readInt32()
|
||||
if (mask and NameType.DEFINITIONS.typeMask != 0) {
|
||||
definitions += tag
|
||||
}
|
||||
if (mask and NameType.NAME_BINDINGS.typeMask != 0) {
|
||||
nameBindings[tag] = readString()
|
||||
}
|
||||
}
|
||||
CachedModuleInfo(
|
||||
artifact = this@fetchModuleInfo,
|
||||
jsIrHeader = JsIrModuleHeader(moduleSafeName, moduleSafeName, definitions, nameBindings, hasJsExports, null),
|
||||
crossModuleReferencesHash = crossModuleReferencesHash
|
||||
)
|
||||
}
|
||||
|
||||
private fun CachedModuleInfo.commitModuleInfo() = artifact.artifactsDir?.let { cacheDir ->
|
||||
File(cacheDir, cacheModuleHeader).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.definitions) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.DEFINITIONS.typeMask) to maskAndName?.second
|
||||
}
|
||||
crossModuleReferencesHash.toProtoStream(this)
|
||||
writeBoolNoTag(jsIrHeader.hasJsExports)
|
||||
writeInt32NoTag(names.size)
|
||||
for ((tag, maskAndName) in names) {
|
||||
writeStringNoTag(tag)
|
||||
writeInt32NoTag(maskAndName.first)
|
||||
if (maskAndName.second != null) {
|
||||
writeStringNoTag(maskAndName.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchCompiledJsCode(artifact: ModuleArtifact) = artifact.artifactsDir?.let { cacheDir ->
|
||||
val jsCode = File(cacheDir, cacheJsModuleFile).ifExists { readText() }
|
||||
val sourceMap = File(cacheDir, cacheJsMapModuleFile).ifExists { readText() }
|
||||
jsCode?.let { CompilationOutputs(it, null, sourceMap) }
|
||||
}
|
||||
|
||||
fun commitCompiledJsCode(artifact: ModuleArtifact, compilationOutputs: CompilationOutputs) = artifact.artifactsDir?.let { cacheDir ->
|
||||
val jsCodeCache = File(cacheDir, cacheJsModuleFile).apply { recreate() }
|
||||
jsCodeCache.writeText(compilationOutputs.jsCode)
|
||||
val jsMapCache = File(cacheDir, cacheJsMapModuleFile)
|
||||
if (compilationOutputs.sourceMap != null) {
|
||||
jsMapCache.recreate()
|
||||
jsMapCache.writeText(compilationOutputs.sourceMap)
|
||||
} else {
|
||||
jsMapCache.ifExists { delete() }
|
||||
}
|
||||
}
|
||||
|
||||
fun loadProgramHeadersFromCache(): List<CachedModuleInfo> {
|
||||
return moduleArtifacts.map { artifact ->
|
||||
fun loadModuleInfo() = CachedModuleInfo(artifact, artifact.loadJsIrModule().makeModuleHeader())
|
||||
val actualInfo = when {
|
||||
artifact.forceRebuildJs -> loadModuleInfo()
|
||||
artifact.fileArtifacts.any { it.isModified() } -> loadModuleInfo()
|
||||
else -> artifact.fetchModuleInfo() ?: loadModuleInfo()
|
||||
}
|
||||
headerToCachedInfo[actualInfo.jsIrHeader] = actualInfo
|
||||
actualInfo
|
||||
}
|
||||
}
|
||||
|
||||
fun loadRequiredJsIrModules(crossModuleReferences: Map<JsIrModuleHeader, CrossModuleReferences>) {
|
||||
for ((header, references) in crossModuleReferences) {
|
||||
val cachedInfo = headerToCachedInfo[header] ?: error("Internal error: cannot find artifact for module ${header.moduleName}")
|
||||
val actualCrossModuleHash = references.crossModuleReferencesHashForIC()
|
||||
if (header.associatedModule == null && cachedInfo.crossModuleReferencesHash != actualCrossModuleHash) {
|
||||
header.associatedModule = cachedInfo.artifact.loadJsIrModule()
|
||||
}
|
||||
header.associatedModule?.let {
|
||||
cachedInfo.crossModuleReferencesHash = actualCrossModuleHash
|
||||
cachedInfo.commitModuleInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ internal fun File.recreate() {
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
internal inline fun File.useCodedInputIfExists(f: CodedInputStream.() -> Unit) = ifExists {
|
||||
internal inline fun <T> File.useCodedInputIfExists(f: CodedInputStream.() -> T) = ifExists {
|
||||
CodedInputStream.newInstance(FileInputStream(this)).f()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.backend.js.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
@@ -144,7 +144,7 @@ class CacheUpdater(
|
||||
}.getFlatHashes()
|
||||
|
||||
val hashProvider = object : InlineFunctionHashProvider {
|
||||
override fun hashForExternalFunction(declaration: IrSimpleFunction): ICHash? {
|
||||
override fun hashForExternalFunction(declaration: IrFunction): ICHash? {
|
||||
return declaration.symbol.signature?.let { cleanInlineHashes[it] }
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ class CacheUpdater(
|
||||
}
|
||||
}
|
||||
|
||||
fun actualizeCaches(callback: (CacheUpdateStatus, String) -> Unit): List<KLibArtifact> {
|
||||
fun actualizeCaches(callback: (CacheUpdateStatus, String) -> Unit): List<ModuleArtifact> {
|
||||
val libraries = loadLibraries()
|
||||
val dependencyGraph = buildDependenciesGraph(libraries)
|
||||
val configHash = compilerConfiguration.configHashForIC()
|
||||
|
||||
+8
-10
@@ -125,7 +125,9 @@ class IrModuleToJsTransformerTmp(
|
||||
return CompilerResult(result, dts)
|
||||
}
|
||||
|
||||
fun generateBinaryAst(files: Iterable<IrFile>, allModules: Iterable<IrModuleFragment>): Map<String, ByteArray> {
|
||||
class SrcFileFragmentAndBinaryAst(val srcPath: String, val fragment: JsIrProgramFragment, val binaryAst: ByteArray)
|
||||
|
||||
fun generateBinaryAst(files: Iterable<IrFile>, allModules: Iterable<IrModuleFragment>): List<SrcFileFragmentAndBinaryAst> {
|
||||
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true)
|
||||
|
||||
val exportData = files.associate { file ->
|
||||
@@ -139,18 +141,14 @@ class IrModuleToJsTransformerTmp(
|
||||
}
|
||||
|
||||
val serializer = JsIrAstSerializer()
|
||||
|
||||
val result = mutableMapOf<String, ByteArray>()
|
||||
files.forEach { f ->
|
||||
return files.map { f ->
|
||||
val exports = exportData[f]!! // TODO
|
||||
val fragment = generateProgramFragment(f, exports, minimizedMemberNames = false)
|
||||
val output = ByteArrayOutputStream()
|
||||
serializer.serialize(fragment, output)
|
||||
val binaryAst = output.toByteArray()
|
||||
result[f.fileEntry.name] = binaryAst
|
||||
SrcFileFragmentAndBinaryAst(f.fileEntry.name, fragment, binaryAst)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun ExportModelGenerator.generateExportWithExternals(irFile: IrFile): List<ExportedDeclaration> {
|
||||
@@ -330,7 +328,7 @@ class IrModuleToJsTransformerTmp(
|
||||
}
|
||||
}
|
||||
|
||||
fun generateWrappedModuleBody(
|
||||
private fun generateWrappedModuleBody(
|
||||
multiModule: Boolean,
|
||||
mainModuleName: String,
|
||||
moduleKind: ModuleKind,
|
||||
@@ -383,7 +381,7 @@ fun generateWrappedModuleBody(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateSingleWrappedModuleBody(
|
||||
fun generateSingleWrappedModuleBody(
|
||||
moduleName: String,
|
||||
moduleKind: ModuleKind,
|
||||
fragments: List<JsIrProgramFragment>,
|
||||
@@ -458,4 +456,4 @@ class SourceMapsInfo(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+97
-60
@@ -27,111 +27,148 @@ class JsIrProgramFragment(val packageFqn: String) {
|
||||
class JsIrModule(
|
||||
val moduleName: String,
|
||||
val externalModuleName: String,
|
||||
val fragments: List<JsIrProgramFragment>,
|
||||
)
|
||||
val fragments: List<JsIrProgramFragment>
|
||||
) {
|
||||
fun makeModuleHeader(): JsIrModuleHeader {
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
val definitions = mutableSetOf<String>()
|
||||
var hasJsExports = false
|
||||
for (fragment in fragments) {
|
||||
hasJsExports = hasJsExports || !fragment.exports.isEmpty
|
||||
for ((tag, name) in fragment.nameBindings.entries) {
|
||||
nameBindings[tag] = name.toString()
|
||||
}
|
||||
definitions += fragment.definitions
|
||||
}
|
||||
return JsIrModuleHeader(moduleName, externalModuleName, definitions, nameBindings, hasJsExports, this)
|
||||
}
|
||||
}
|
||||
|
||||
class JsIrModuleHeader(
|
||||
val moduleName: String,
|
||||
val externalModuleName: String,
|
||||
val definitions: Set<String>,
|
||||
val nameBindings: Map<String, String>,
|
||||
val hasJsExports: Boolean,
|
||||
var associatedModule: JsIrModule?
|
||||
) {
|
||||
val externalNames: Set<String> by lazy { nameBindings.keys - definitions }
|
||||
}
|
||||
|
||||
class JsIrProgram(val modules: List<JsIrModule>) {
|
||||
val mainModule = modules.last()
|
||||
val otherModules = modules.dropLast(1)
|
||||
|
||||
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
|
||||
val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
|
||||
val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() })
|
||||
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
|
||||
return crossModuleReferences.entries.associate {
|
||||
val module = it.key.associatedModule ?: error("Internal error: module ${it.key.moduleName} is not loaded")
|
||||
it.value.initJsImportsForModule(module)
|
||||
module to it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CrossModuleDependenciesResolver(private val headers: List<JsIrModuleHeader>) {
|
||||
fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> {
|
||||
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
|
||||
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
|
||||
|
||||
moduleToBuilder[mainModule]!!.transitiveJsExportFrom = otherModules
|
||||
for (module in modules) {
|
||||
val moduleBuilder = moduleToBuilder[module]!!
|
||||
for (fragment in module.fragments) {
|
||||
for (definition in fragment.definitions) {
|
||||
require(definition !in definitionModule) { "Duplicate definition: $definition" }
|
||||
definitionModule[definition] = moduleBuilder
|
||||
}
|
||||
val mainModuleHeader = headers.last()
|
||||
val otherModuleHeaders = headers.dropLast(1)
|
||||
headerToBuilder[mainModuleHeader]!!.transitiveJsExportFrom = otherModuleHeaders
|
||||
|
||||
for (header in headers) {
|
||||
val builder = headerToBuilder[header]!!
|
||||
for (definition in header.definitions) {
|
||||
require(definition !in definitionModule) { "Duplicate definition: $definition" }
|
||||
definitionModule[definition] = builder
|
||||
}
|
||||
}
|
||||
|
||||
for (module in modules) {
|
||||
val moduleBuilder = moduleToBuilder[module]!!
|
||||
for (fragment in module.fragments) {
|
||||
for (tag in fragment.nameBindings.keys) {
|
||||
val fromModuleBuilder = definitionModule[tag] ?: continue // TODO error?
|
||||
if (fromModuleBuilder == moduleBuilder) continue
|
||||
for (header in headers) {
|
||||
val builder = headerToBuilder[header]!!
|
||||
for (tag in header.externalNames) {
|
||||
val fromModuleBuilder = definitionModule[tag] ?: continue // TODO error?
|
||||
|
||||
moduleBuilder.imports += CrossModuleRef(fromModuleBuilder, tag)
|
||||
fromModuleBuilder.exports += tag
|
||||
}
|
||||
builder.imports += CrossModuleRef(fromModuleBuilder, tag)
|
||||
fromModuleBuilder.exports += tag
|
||||
}
|
||||
}
|
||||
|
||||
return modules.associateWith { moduleToBuilder[it]!!.buildCrossModuleRefs() }
|
||||
return headers.associateWith { headerToBuilder[it]!!.buildCrossModuleRefs() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun String.prettyTag() = takeWhile { c -> c != '|' }
|
||||
|
||||
private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String)
|
||||
|
||||
|
||||
private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, val relativeRequirePath: Boolean) {
|
||||
private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHeader, val relativeRequirePath: Boolean) {
|
||||
val imports = mutableListOf<CrossModuleRef>()
|
||||
val exports = mutableSetOf<String>()
|
||||
var transitiveJsExportFrom = emptyList<JsIrModule>()
|
||||
var transitiveJsExportFrom = emptyList<JsIrModuleHeader>()
|
||||
|
||||
private lateinit var exportNames: Map<String, String> // tag -> index
|
||||
|
||||
private fun buildUniqueNames() {
|
||||
val result = mutableMapOf<String, String>()
|
||||
|
||||
private fun buildExportNames() {
|
||||
var index = 0
|
||||
|
||||
exports.sorted().forEach { tag ->
|
||||
result[tag] = index++.toJsIdentifier()
|
||||
}
|
||||
|
||||
exportNames = result
|
||||
exportNames = exports.sorted().associateWith { index++.toJsIdentifier() }
|
||||
}
|
||||
|
||||
fun buildCrossModuleRefs(): CrossModuleReferences {
|
||||
buildUniqueNames()
|
||||
buildExportNames()
|
||||
val importedModules = mutableMapOf<JsIrModuleHeader, JsImportedModule>()
|
||||
|
||||
val importedModules = mutableMapOf<JsIrModule, JsImportedModule>()
|
||||
|
||||
fun JsIrModule.import(): JsName {
|
||||
return importedModules.getOrPut(this) {
|
||||
val moduleName = JsName(moduleName, false)
|
||||
JsImportedModule(externalModuleName, moduleName, null, relativeRequirePath)
|
||||
fun import(moduleHeader: JsIrModuleHeader): JsName {
|
||||
return importedModules.getOrPut(moduleHeader) {
|
||||
val jsModuleName = JsName(moduleHeader.moduleName, false)
|
||||
JsImportedModule(moduleHeader.externalModuleName, jsModuleName, null, relativeRequirePath)
|
||||
}.internalName
|
||||
}
|
||||
|
||||
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
|
||||
|
||||
val resultImports = imports.associate {
|
||||
val tag = it.tag
|
||||
require(it.module::exportNames.isInitialized) {
|
||||
val resultImports = imports.associate { crossModuleRef ->
|
||||
val tag = crossModuleRef.tag
|
||||
require(crossModuleRef.module::exportNames.isInitialized) {
|
||||
// This situation appears in case of a dependent module redefine a symbol (function) from their dependency
|
||||
"Cross module dependency resolution failed due to symbol '${tag.takeWhile { c -> c != '|' }}' redefinition"
|
||||
"Cross module dependency resolution failed due to symbol '${tag.prettyTag()}' redefinition"
|
||||
}
|
||||
val exportedAs = it.module.exportNames[tag]!!
|
||||
val importedAs = tagToName[tag]!!
|
||||
val moduleName = it.module.module.import()
|
||||
val exportedAs = crossModuleRef.module.exportNames[tag]!!
|
||||
val moduleName = import(crossModuleRef.module.header)
|
||||
|
||||
val importStatement = JsVars.JsVar(importedAs, JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleName)))
|
||||
|
||||
tag to importStatement
|
||||
tag to CrossModuleImport(exportedAs, moduleName)
|
||||
}
|
||||
|
||||
val transitiveExport = transitiveJsExportFrom.mapNotNull {
|
||||
it.fragments.find { f -> !f.exports.isEmpty }?.run { it.import() }
|
||||
if (it.hasJsExports) import(it) else null
|
||||
}
|
||||
return CrossModuleReferences(importedModules.values.toList(), resultImports, exportNames, transitiveExport)
|
||||
return CrossModuleReferences(importedModules.values.toList(), transitiveExport, exportNames, resultImports)
|
||||
}
|
||||
}
|
||||
|
||||
class CrossModuleImport(val exportedAs: String, val moduleExporter: JsName)
|
||||
|
||||
class CrossModuleReferences(
|
||||
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
|
||||
val imports: Map<String, JsVars.JsVar>, // tag -> import statement
|
||||
val transitiveJsExportFrom: List<JsName>, // the list of modules which provide their js exports for transitive export
|
||||
val exports: Map<String, String>, // tag -> index
|
||||
val transitiveJsExportFrom: List<JsName> // the list of modules which provide their js exports for transitive export
|
||||
val imports: Map<String, CrossModuleImport>, // tag -> import statement
|
||||
) {
|
||||
companion object {
|
||||
val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap(), emptyList())
|
||||
// built from imports
|
||||
var jsImports = emptyMap<String, JsVars.JsVar>() // 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 symbol ${it.key.prettyTag()}")
|
||||
val exportRef = JsNameRef(it.value.exportedAs, ReservedJsNames.makeCrossModuleNameRef(it.value.moduleExporter))
|
||||
it.key to JsVars.JsVar(importedAs, exportRef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Empty = CrossModuleReferences(listOf(), emptyList(), emptyMap(), emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ class Merger(
|
||||
}
|
||||
}
|
||||
|
||||
for ((tag, jsVar) in crossModuleReferences.imports) {
|
||||
for ((tag, jsVar) in crossModuleReferences.jsImports) {
|
||||
val importName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
|
||||
|
||||
importStatements.putIfAbsent(tag, JsVars(JsVars.JsVar(importName, jsVar.initExpression)))
|
||||
@@ -302,4 +302,4 @@ class Merger(
|
||||
this += statements
|
||||
endRegion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.regex.Pattern
|
||||
|
||||
class ProjectInfo(val name: String, val modules: List<String>, val steps: List<ProjectBuildStep>, val muted: Boolean) {
|
||||
|
||||
class ProjectBuildStep(val id: Int, val order: List<String>)
|
||||
class ProjectBuildStep(val id: Int, val order: List<String>, val dirtyJS: List<String>)
|
||||
}
|
||||
|
||||
class ModuleInfo(val moduleName: String) {
|
||||
@@ -52,14 +52,15 @@ class ModuleInfo(val moduleName: String) {
|
||||
}
|
||||
|
||||
enum class StepDirectives(val mnemonic: String) {
|
||||
FAST_PATH_UPDATE("FP")
|
||||
FAST_PATH_UPDATE("FP"),
|
||||
UNUSED_MODULE("UNUSED")
|
||||
}
|
||||
|
||||
const val MODULES_LIST = "MODULES"
|
||||
const val PROJECT_INFO_FILE = "project.info"
|
||||
const val MODULE_INFO_FILE = "module.info"
|
||||
|
||||
private val STEP_PATTERN = Pattern.compile("^\\s*STEP\\s+(\\d+)\\s*:?$")
|
||||
private val STEP_PATTERN = Pattern.compile("^\\s*STEP\\s+(\\d+)\\.*(\\d+)?\\s*:?$")
|
||||
|
||||
private val MODIFICATION_PATTERN = Pattern.compile("^([UD])\\s*:(.+)$")
|
||||
|
||||
@@ -94,11 +95,14 @@ abstract class InfoParser<Info>(protected val infoFile: File) {
|
||||
|
||||
}
|
||||
|
||||
private fun String.splitAndTrim() = split(",").map { it.trim() }.filter { it.isNotBlank() }
|
||||
|
||||
class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
|
||||
private fun parseStep(stepId: Int): ProjectInfo.ProjectBuildStep {
|
||||
private fun parseSteps(firstId: Int, lastId: Int): List<ProjectInfo.ProjectBuildStep> {
|
||||
val order = mutableListOf<String>()
|
||||
val dirtyJS = mutableListOf<String>()
|
||||
|
||||
loop { line ->
|
||||
val splitIndex = line.indexOf(':')
|
||||
@@ -113,10 +117,13 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
++lineCounter
|
||||
|
||||
|
||||
when (op) {
|
||||
"libs" -> {
|
||||
val args = splitted[1]
|
||||
args.split(",").filter { it.isNotBlank() }.forEach { order.add(it.trim()) }
|
||||
order += splitted[1].splitAndTrim()
|
||||
}
|
||||
"dirty js" -> {
|
||||
dirtyJS += splitted[1].splitAndTrim()
|
||||
}
|
||||
else -> println(diagnosticMessage("Unknown op $op", line))
|
||||
}
|
||||
@@ -124,7 +131,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
false
|
||||
}
|
||||
|
||||
return ProjectInfo.ProjectBuildStep(stepId, order)
|
||||
return (firstId..lastId).map { ProjectInfo.ProjectBuildStep(it, order, dirtyJS) }
|
||||
}
|
||||
|
||||
override fun parse(entryName: String): ProjectInfo {
|
||||
@@ -148,14 +155,15 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
|
||||
|
||||
when {
|
||||
op == MODULES_LIST -> {
|
||||
val arguments = splitted[1]
|
||||
arguments.split(",").filter { it.isNotBlank() }.forEach { libraries.add(it.trim()) }
|
||||
libraries += splitted[1].splitAndTrim()
|
||||
}
|
||||
op.matches(STEP_PATTERN.toRegex()) -> {
|
||||
val m = STEP_PATTERN.matcher(op)
|
||||
if (!m.matches()) throwSyntaxError(line)
|
||||
val stepId = Integer.parseInt(m.group(1))
|
||||
steps.add(parseStep(stepId))
|
||||
|
||||
val firstId = Integer.parseInt(m.group(1))
|
||||
val lastId = m.group(2)?.let { Integer.parseInt(it) } ?: firstId
|
||||
steps += parseSteps(firstId, lastId)
|
||||
}
|
||||
else -> println(diagnosticMessage("Unknown op $op", line))
|
||||
}
|
||||
@@ -202,7 +210,7 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStep(stepId: Int): ModuleInfo.ModuleStep {
|
||||
private fun parseSteps(firstId: Int, lastId: Int): List<ModuleInfo.ModuleStep> {
|
||||
val dependencies = mutableSetOf<String>()
|
||||
val dirtyFiles = mutableSetOf<String>()
|
||||
val modifications = mutableListOf<ModuleInfo.Modification>()
|
||||
@@ -227,7 +235,7 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
|
||||
false
|
||||
}
|
||||
|
||||
return ModuleInfo.ModuleStep(stepId, dependencies, dirtyFiles, modifications, directives)
|
||||
return (firstId..lastId).map { ModuleInfo.ModuleStep(it, dependencies, dirtyFiles, modifications, directives) }
|
||||
}
|
||||
|
||||
override fun parse(entryName: String): ModuleInfo {
|
||||
@@ -237,8 +245,9 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
|
||||
lineCounter++
|
||||
val stepMatcher = STEP_PATTERN.matcher(line)
|
||||
if (stepMatcher.matches()) {
|
||||
val id = Integer.parseInt(stepMatcher.group(1))
|
||||
result.steps.add(parseStep(id))
|
||||
val firstId = Integer.parseInt(stepMatcher.group(1))
|
||||
val lastId = stepMatcher.group(2)?.let { Integer.parseInt(it) } ?: firstId
|
||||
result.steps += parseSteps(firstId, lastId)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user