[JS IR] IC: support per-module layout

This commit is contained in:
Anton Bannykh
2021-12-08 14:21:12 +03:00
parent 434c23a343
commit 4d8926cf26
17 changed files with 110 additions and 23 deletions
@@ -5,10 +5,8 @@
package org.jetbrains.kotlin.ir.backend.js package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.backend.common.lower
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.PhaserState import org.jetbrains.kotlin.backend.common.phaser.PhaserState
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.ic.ModuleCache import org.jetbrains.kotlin.ir.backend.js.ic.ModuleCache
@@ -17,8 +15,8 @@ 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.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker 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.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.backend.js.utils.serialization.JsIrAstDeserializer
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.descriptors.IrBuiltInsOverDescriptors import org.jetbrains.kotlin.ir.descriptors.IrBuiltInsOverDescriptors
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
@@ -107,6 +105,8 @@ fun compileWithIC(
val ast = transformer.generateBinaryAst(dirtyFiles) val ast = transformer.generateBinaryAst(dirtyFiles)
ast.entries.forEach { (path, bytes) -> cacheConsumer.commitBinaryAst(path, bytes) } ast.entries.forEach { (path, bytes) -> cacheConsumer.commitBinaryAst(path, bytes) }
cacheConsumer.commitModuleName(module.name.asString())
} }
fun lowerPreservingTags(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext, phaseConfig: PhaseConfig, controller: WholeWorldStageController) { fun lowerPreservingTags(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext, phaseConfig: PhaseConfig, controller: WholeWorldStageController) {
@@ -131,15 +131,21 @@ fun generateJsFromAst(
caches: Map<String, ModuleCache>, caches: Map<String, ModuleCache>,
): CompilerResult { ): CompilerResult {
val deserializer = JsIrAstDeserializer() val deserializer = JsIrAstDeserializer()
val fragments = JsIrProgram(caches.values.map { JsIrModule(it.name, it.name, it.asts.values.sortedBy { it.name }.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it))} }) }) val jsIrProgram = JsIrProgram(caches.values.map {
JsIrModule(
it.name.safeModuleName,
sanitizeName(it.name.safeModuleName),
it.asts.values.sortedBy { it.name }.mapNotNull { it.ast?.let { deserializer.deserialize(ByteArrayInputStream(it)) } })
})
return CompilerResult( return CompilerResult(
generateSingleWrappedModuleBody( generateWrappedModuleBody(
mainModuleName, multiModule = true,
moduleKind, mainModuleName = mainModuleName,
fragments.modules.flatMap { it.fragments }, moduleKind = moduleKind,
jsIrProgram,
sourceMapsInfo = null, sourceMapsInfo = null,
relativeRequirePath = false,
generateScriptModule = false, generateScriptModule = false,
generateCallToMain = true,
), null ), null
) )
} }
@@ -283,7 +283,7 @@ class IrModuleToJsTransformerTmp(
} }
} }
private fun generateWrappedModuleBody( fun generateWrappedModuleBody(
multiModule: Boolean, multiModule: Boolean,
mainModuleName: String, mainModuleName: String,
moduleKind: ModuleKind, moduleKind: ModuleKind,
@@ -336,7 +336,7 @@ private fun generateWrappedModuleBody(
} }
} }
fun generateSingleWrappedModuleBody( private fun generateSingleWrappedModuleBody(
moduleName: String, moduleName: String,
moduleKind: ModuleKind, moduleKind: ModuleKind,
fragments: List<JsIrProgramFragment>, fragments: List<JsIrProgramFragment>,
@@ -117,8 +117,11 @@ fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>): CrossMod
} }
val IrModuleFragment.safeName: String val IrModuleFragment.safeName: String
get() = name.asString().safeModuleName
val String.safeModuleName: String
get() { get() {
var result = name.asString() var result = this
if (result.startsWith('<')) result = result.substring(1) if (result.startsWith('<')) result = result.substring(1)
if (result.endsWith('>')) result = result.substring(0, result.length - 1) if (result.endsWith('>')) result = result.substring(0, result.length - 1)
@@ -52,6 +52,8 @@ interface PersistentCacheProvider {
fun filePaths(): Iterable<String> fun filePaths(): Iterable<String>
fun moduleName(): String
companion object { companion object {
val EMPTY = object : PersistentCacheProvider { val EMPTY = object : PersistentCacheProvider {
override fun fileFingerPrint(path: String): Hash { override fun fileFingerPrint(path: String): Hash {
@@ -87,6 +89,10 @@ interface PersistentCacheProvider {
} }
override fun filePaths(): Iterable<String> = emptyList() override fun filePaths(): Iterable<String> = emptyList()
override fun moduleName(): String {
TODO("Not yet implemented")
}
} }
} }
} }
@@ -192,6 +198,10 @@ class PersistentCacheProviderImpl(private val cachePath: String) : PersistentCac
} else null } else null
} }
} }
override fun moduleName(): String {
TODO("Not yet implemented")
}
} }
interface PersistentCacheConsumer { interface PersistentCacheConsumer {
@@ -205,6 +215,7 @@ interface PersistentCacheConsumer {
fun invalidateForFile(path: String) fun invalidateForFile(path: String)
fun commitLibraryPath(libraryPath: String, flatHash: ULong, transHash: ULong) fun commitLibraryPath(libraryPath: String, flatHash: ULong, transHash: ULong)
fun commitModuleName(moduleName: String)
companion object { companion object {
val EMPTY = object : PersistentCacheConsumer { val EMPTY = object : PersistentCacheConsumer {
@@ -246,6 +257,9 @@ interface PersistentCacheConsumer {
override fun commitSourceMap(path: String, mapData: ByteArray) { override fun commitSourceMap(path: String, mapData: ByteArray) {
} }
override fun commitModuleName(moduleName: String) {
}
} }
} }
} }
@@ -347,4 +361,8 @@ class PersistentCacheConsumerImpl(private val cachePath: String) : PersistentCac
it.println(transHash.toString(16)) it.println(transHash.toString(16))
} }
} }
override fun commitModuleName(moduleName: String) {
TODO("Not yet implemented")
}
} }
@@ -239,4 +239,11 @@ object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() {
description = "", description = "",
applicability = DirectiveApplicability.Global applicability = DirectiveApplicability.Global
) )
val NO_COMMON_FILES by directive(
"""
Don't added helper files to prevent linking issues.
""".trimIndent(),
applicability = DirectiveApplicability.Global,
)
} }
@@ -53,6 +53,7 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
const val OLD_MODULE_SUFFIX = "_old" const val OLD_MODULE_SUFFIX = "_old"
private const val OUTPUT_DIR_NAME = "outputDir" private const val OUTPUT_DIR_NAME = "outputDir"
private const val RECOMPILED_OUTPUT_DIR_NAME = "outputDir-recompiled"
private const val OUTPUT_KLIB_DIR_NAME = "outputKlibDir" private const val OUTPUT_KLIB_DIR_NAME = "outputKlibDir"
private const val DCE_OUTPUT_DIR_NAME = "dceOutputDir" private const val DCE_OUTPUT_DIR_NAME = "dceOutputDir"
private const val MINIFICATION_OUTPUT_DIR_NAME = "minOutputDir" private const val MINIFICATION_OUTPUT_DIR_NAME = "minOutputDir"
@@ -80,6 +81,10 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
return getJsArtifactsOutputDir(testServices).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) + "_v5" return getJsArtifactsOutputDir(testServices).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) + "_v5"
} }
fun getRecompiledJsModuleArtifactPath(testServices: TestServices, moduleName: String): String {
return getJsArtifactsRecompiledOutputDir(testServices).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) + "_v5"
}
fun getJsKlibArtifactPath(testServices: TestServices, moduleName: String): String { fun getJsKlibArtifactPath(testServices: TestServices, moduleName: String): String {
return getJsKlibOutputDir(testServices).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) return getJsKlibOutputDir(testServices).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName)
} }
@@ -92,6 +97,10 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
return testServices.temporaryDirectoryManager.getOrCreateTempDirectory(OUTPUT_DIR_NAME) return testServices.temporaryDirectoryManager.getOrCreateTempDirectory(OUTPUT_DIR_NAME)
} }
fun getJsArtifactsRecompiledOutputDir(testServices: TestServices): File {
return testServices.temporaryDirectoryManager.getOrCreateTempDirectory(RECOMPILED_OUTPUT_DIR_NAME)
}
fun getJsKlibOutputDir(testServices: TestServices): File { fun getJsKlibOutputDir(testServices: TestServices): File {
return testServices.temporaryDirectoryManager.getOrCreateTempDirectory(OUTPUT_KLIB_DIR_NAME) return testServices.temporaryDirectoryManager.getOrCreateTempDirectory(OUTPUT_KLIB_DIR_NAME)
} }
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.js.test
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.js.JavaScript import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
@@ -18,6 +19,7 @@ import java.io.FileFilter
class JsAdditionalSourceProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) { class JsAdditionalSourceProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) {
override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> { override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> {
if (JsEnvironmentConfigurationDirectives.NO_COMMON_FILES in module.directives) return emptyList()
return getAdditionalKotlinFiles(module.files.first().originalFile.parent).map { it.toTestFile() } return getAdditionalKotlinFiles(module.files.first().originalFile.parent).map { it.toTestFile() }
} }
@@ -92,8 +92,7 @@ class JsIrBackendFacade(
val outputFile = if (firstTimeCompilation) { val outputFile = if (firstTimeCompilation) {
File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js") File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js")
} else { } else {
val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js") File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name) + ".js")
File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
} }
val moduleName = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) val moduleName = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)
val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN) val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
@@ -296,8 +295,7 @@ class JsIrBackendFacade(
dependencies.forEach { (moduleId, outputs) -> dependencies.forEach { (moduleId, outputs) ->
val moduleWrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(outputs.jsCode, moduleKind, moduleId) val moduleWrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(outputs.jsCode, moduleKind, moduleId)
val dependencyPath = outputFile.absolutePath.replace("_v5.js", "-${moduleId}_v5.js") outputFile.augmentWithModuleName(moduleId).write(moduleWrappedCode)
File(dependencyPath).write(moduleWrappedCode)
} }
} }
@@ -326,4 +324,9 @@ class JsIrBackendFacade(
override fun shouldRunAnalysis(module: TestModule): Boolean { override fun shouldRunAnalysis(module: TestModule): Boolean {
return JsEnvironmentConfigurator.isMainModule(module, testServices) return JsEnvironmentConfigurator.isMainModule(module, testServices)
} }
}
fun File.augmentWithModuleName(moduleName: String): File {
check(absolutePath.endsWith("_v5.js"))
return File(absolutePath.removeSuffix("_v5.js") + "-${moduleName}_v5.js")
} }
@@ -6,11 +6,15 @@
package org.jetbrains.kotlin.js.test.handlers package org.jetbrains.kotlin.js.test.handlers
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.fir.backend.FirMetadataSource
import org.jetbrains.kotlin.js.test.converters.ClassicJsBackendFacade
import org.jetbrains.kotlin.js.test.converters.augmentWithModuleName
import org.jetbrains.kotlin.test.backend.handlers.JsBinaryArtifactHandler import org.jetbrains.kotlin.test.backend.handlers.JsBinaryArtifactHandler
import org.jetbrains.kotlin.test.model.BinaryArtifacts.Js import org.jetbrains.kotlin.test.model.BinaryArtifacts.Js
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions import org.jetbrains.kotlin.test.services.assertions
import java.io.File
class JsIrRecompiledArtifactsIdentityHandler(testServices: TestServices) : JsBinaryArtifactHandler(testServices) { class JsIrRecompiledArtifactsIdentityHandler(testServices: TestServices) : JsBinaryArtifactHandler(testServices) {
override fun processModule(module: TestModule, info: Js) { override fun processModule(module: TestModule, info: Js) {
@@ -46,10 +50,28 @@ class JsIrRecompiledArtifactsIdentityHandler(testServices: TestServices) : JsBin
} }
} }
val originalOutput = FileUtil.loadFile(originalArtifact.outputFile) val originalFilesToCheck = originalArtifact.allFiles()
val recompiledOutput = FileUtil.loadFile(incrementalArtifact.outputFile) val recompiledFilesToCheck = incrementalArtifact.allFiles()
testServices.assertions.assertEquals(originalOutput, recompiledOutput) { "Output file changed after recompilation" }
testServices.assertions.assertEquals(originalFilesToCheck.size, recompiledFilesToCheck.size)
for ((originalFile, recompiledFile) in originalFilesToCheck.zip(recompiledFilesToCheck)) {
testServices.assertions.assertEquals(originalFile.name, recompiledFile.name)
val originalOutput = FileUtil.loadFile(originalFile)
val recompiledOutput = FileUtil.loadFile(recompiledFile)
testServices.assertions.assertEquals(originalOutput, recompiledOutput) {
"Output file changed after recompilation"
}
}
} }
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
} }
private fun Js.JsIrArtifact.allFiles(): Collection<File> {
return listOf(outputFile) + compilerResult.outputs!!.dependencies.map { (moduleId, _) ->
outputFile.augmentWithModuleName(moduleId)
}.sortedBy { it.name }
}
@@ -25,9 +25,11 @@ import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurato
import org.jetbrains.kotlin.test.services.jsLibraryProvider import org.jetbrains.kotlin.test.services.jsLibraryProvider
import java.io.File import java.io.File
class TestModuleCache(val moduleName: String, val files: MutableMap<String, FileCache>) { class TestModuleCache(val files: MutableMap<String, FileCache>) {
constructor(moduleName: String) : this(moduleName, mutableMapOf()) constructor() : this(mutableMapOf())
private lateinit var storedModuleName: String
fun cacheProvider(): PersistentCacheProvider { fun cacheProvider(): PersistentCacheProvider {
return object : PersistentCacheProvider { return object : PersistentCacheProvider {
@@ -66,6 +68,10 @@ class TestModuleCache(val moduleName: String, val files: MutableMap<String, File
override fun filePaths(): Iterable<String> { override fun filePaths(): Iterable<String> {
return files.keys return files.keys
} }
override fun moduleName(): String {
return storedModuleName
}
} }
} }
@@ -117,10 +123,14 @@ class TestModuleCache(val moduleName: String, val files: MutableMap<String, File
override fun commitLibraryPath(libraryPath: String, flatHash: ULong, transHash: ULong) { override fun commitLibraryPath(libraryPath: String, flatHash: ULong, transHash: ULong) {
} }
override fun commitModuleName(moduleName: String) {
storedModuleName = moduleName
}
} }
} }
fun createModuleCache(): ModuleCache = ModuleCache(moduleName, files) fun createModuleCache(): ModuleCache = ModuleCache(storedModuleName, files)
} }
class JsIrIncrementalDataProvider(private val testServices: TestServices) : TestService { class JsIrIncrementalDataProvider(private val testServices: TestServices) : TestService {
@@ -201,7 +211,7 @@ class JsIrIncrementalDataProvider(private val testServices: TestServices) : Test
var moduleCache = predefinedKlibHasIcCache[canonicalPath] var moduleCache = predefinedKlibHasIcCache[canonicalPath]
if (moduleCache == null) { if (moduleCache == null) {
moduleCache = icCache[canonicalPath] ?: TestModuleCache(canonicalPath) moduleCache = icCache[canonicalPath] ?: TestModuleCache()
val libs = allDependencies.associateBy { File(it.libraryFile.path).canonicalPath } val libs = allDependencies.associateBy { File(it.libraryFile.path).canonicalPath }
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1282 // EXPECTED_REACHABLE_NODES: 1282
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
+1
View File
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1283 // EXPECTED_REACHABLE_NODES: 1283
// MODULE: lib // MODULE: lib
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1286 // EXPECTED_REACHABLE_NODES: 1286
// MODULE: lib1 // MODULE: lib1
// FILE: lib1.kt // FILE: lib1.kt
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1283 // EXPECTED_REACHABLE_NODES: 1283
// MODULE: lib // MODULE: lib
// FILE: a.kt // FILE: a.kt
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1282 // EXPECTED_REACHABLE_NODES: 1282
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
@@ -1,3 +1,4 @@
// NO_COMMON_FILES
// EXPECTED_REACHABLE_NODES: 1284 // EXPECTED_REACHABLE_NODES: 1284
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt