[JS IC] Support //RECOMPILE directive in js box tests

- change test runner to production mode when sources are being compiled
 into klib and then klib is being translated into js, not directly from
 kt to js
 - fix IC cache format
 - support IC tests
This commit is contained in:
Roman Artemev
2021-09-09 15:44:14 +03:00
committed by teamcityserver
parent 6b2fee7143
commit a2e4ebd820
8 changed files with 225 additions and 118 deletions
@@ -38,9 +38,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCacheForModule import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.ic.buildCache
import org.jetbrains.kotlin.ir.backend.js.ic.checkCaches
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
@@ -76,6 +74,10 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
private fun usePerFileInvalidator(configuration: CompilerConfiguration): Boolean = false private fun usePerFileInvalidator(configuration: CompilerConfiguration): Boolean = false
private fun IcCacheInfo.toICCacheMap(): Map<String, ICCache> {
return data.map { it.key to ICCache(PersistentCacheProvider.EMPTY, PersistentCacheConsumer.EMPTY, it.value) }.toMap()
}
override fun doExecute( override fun doExecute(
arguments: K2JSCompilerArguments, arguments: K2JSCompilerArguments,
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
@@ -234,7 +236,8 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
// Run analysis if main module is sources // Run analysis if main module is sources
lateinit var sourceModule: ModulesStructure lateinit var sourceModule: ModulesStructure
if (arguments.includes == null) { val includes = arguments.includes
if (includes == null) {
do { do {
sourceModule = prepareAnalyzedSourceModule( sourceModule = prepareAnalyzedSourceModule(
projectJs, projectJs,
@@ -245,7 +248,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
AnalyzerWithCompilerReport(config.configuration), AnalyzerWithCompilerReport(config.configuration),
icUseGlobalSignatures = icCaches.isNotEmpty(), icUseGlobalSignatures = icCaches.isNotEmpty(),
icUseStdlibCache = icCaches.isNotEmpty(), icUseStdlibCache = icCaches.isNotEmpty(),
icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = arguments.includes).data else emptyMap() icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = includes).toICCacheMap() else emptyMap()
) )
val result = sourceModule.jsFrontEndResult.jsAnalysisResult val result = sourceModule.jsFrontEndResult.jsAnalysisResult
if (result is JsAnalysisResult.RetryWithAdditionalRoots) { if (result is JsAnalysisResult.RetryWithAdditionalRoots) {
@@ -276,8 +279,6 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val phaseConfig = createPhaseConfig(jsPhases, arguments, messageCollector) val phaseConfig = createPhaseConfig(jsPhases, arguments, messageCollector)
val includes = arguments.includes
val module = if (includes != null) { val module = if (includes != null) {
if (sourcesFiles.isNotEmpty()) { if (sourcesFiles.isNotEmpty()) {
messageCollector.report(ERROR, "Source files are not supported when -Xinclude is present") messageCollector.report(ERROR, "Source files are not supported when -Xinclude is present")
@@ -294,7 +295,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
friendLibraries, friendLibraries,
icUseGlobalSignatures = icCaches.isNotEmpty(), icUseGlobalSignatures = icCaches.isNotEmpty(),
icUseStdlibCache = icCaches.isNotEmpty(), icUseStdlibCache = icCaches.isNotEmpty(),
icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = includes).data else emptyMap() icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = includes).toICCacheMap() else emptyMap()
) )
} else { } else {
sourceModule sourceModule
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.ic package org.jetbrains.kotlin.ir.backend.js.ic
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.AbstractAnalyzerWithCompilerReport
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.MainModule import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.toByteArray import org.jetbrains.kotlin.ir.backend.js.toByteArray
@@ -20,6 +19,10 @@ import kotlin.random.nextULong
// TODO: Proper version of the compiler (should take changes to lowerings into account) // TODO: Proper version of the compiler (should take changes to lowerings into account)
private val compilerVersion = Random.nextULong() private val compilerVersion = Random.nextULong()
private fun IcCacheInfo.toICCacheMap(): Map<String, ICCache> {
return data.map { it.key to ICCache(PersistentCacheProvider.EMPTY, PersistentCacheConsumer.EMPTY, it.value) }.toMap()
}
// TODO more parameters for lowerings // TODO more parameters for lowerings
// Returns true if caches were built. False if caches were up-to-date. // Returns true if caches were built. False if caches were up-to-date.
fun buildCache( fun buildCache(
@@ -50,9 +53,9 @@ fun buildCache(
File(icDir, "info").delete() File(icDir, "info").delete()
icDir.mkdirs() icDir.mkdirs()
val icData = prepareSingleLibraryIcCache(project, configuration, mainModule.libPath, dependencies, friendDependencies, exportedDeclarations, icCache.data) val icData = prepareSingleLibraryIcCache(project, configuration, mainModule.libPath, dependencies, friendDependencies, exportedDeclarations, icCache.toICCacheMap())
icData.writeTo(File(cachePath)) icData.serializedIcData.writeTo(File(cachePath))
CacheInfo(cachePath, mainModule.libPath, md5).save() CacheInfo(cachePath, mainModule.libPath, md5).save()
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.ic package org.jetbrains.kotlin.ir.backend.js.ic
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.AbstractAnalyzerWithCompilerReport
import org.jetbrains.kotlin.backend.common.lower import org.jetbrains.kotlin.backend.common.lower
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
@@ -34,8 +33,8 @@ fun prepareSingleLibraryIcCache(
dependencies: Collection<String>, dependencies: Collection<String>,
friendDependencies: Collection<String> = emptyList(), friendDependencies: Collection<String> = emptyList(),
exportedDeclarations: Set<FqName> = emptySet(), exportedDeclarations: Set<FqName> = emptySet(),
icCache: Map<String, SerializedIcData> = emptyMap(), icCache: Map<String, ICCache> = emptyMap(),
): SerializedIcData { ): ICCache {
val irFactory = PersistentIrFactory() val irFactory = PersistentIrFactory()
val controller = WholeWorldStageController() val controller = WholeWorldStageController()
irFactory.stageController = controller irFactory.stageController = controller
@@ -67,13 +66,17 @@ fun prepareSingleLibraryIcCache(
lowerPreservingIcData(moduleFragment, context, controller) lowerPreservingIcData(moduleFragment, context, controller)
return IcSerializer( return ICCache(
context.irBuiltIns, PersistentCacheProvider.EMPTY,
context.mapping, PersistentCacheConsumer.EMPTY,
irFactory, IcSerializer(
deserializer, context.irBuiltIns,
moduleFragment context.mapping,
).serializeDeclarations(irFactory.allDeclarations) irFactory,
deserializer,
moduleFragment
).serializeDeclarations(irFactory.allDeclarations)
)
} }
private fun KotlinResolvedLibrary.allDependencies(): List<KotlinResolvedLibrary> { private fun KotlinResolvedLibrary.allDependencies(): List<KotlinResolvedLibrary> {
@@ -265,7 +265,8 @@ private fun createCacheConsumer(path: String): PersistentCacheConsumer {
private fun loadCacheInfo(cachePaths: Collection<String>): MutableMap<ModulePath, String> { private fun loadCacheInfo(cachePaths: Collection<String>): MutableMap<ModulePath, String> {
val caches = cachePaths.map { CacheInfo.load(it) ?: error("Cannot load IC cache from $it") } val caches = cachePaths.map { CacheInfo.load(it) ?: error("Cannot load IC cache from $it") }
return caches.associate { it.libPath.toCanonicalPath() to it.path } as MutableMap<ModulePath, String> val result = mutableMapOf<ModulePath, String>()
return caches.associateTo(result) { it.libPath.toCanonicalPath() to it.path }
} }
private fun loadLibraries(configuration: CompilerConfiguration, dependencies: Collection<String>): Map<ModulePath, KotlinLibrary> { private fun loadLibraries(configuration: CompilerConfiguration, dependencies: Collection<String>): Map<ModulePath, KotlinLibrary> {
@@ -298,7 +299,7 @@ fun actualizeCacheForModule(
val libraries: Map<ModulePath, KotlinLibrary> = loadLibraries(compilerConfiguration, dependencies) val libraries: Map<ModulePath, KotlinLibrary> = loadLibraries(compilerConfiguration, dependencies)
val persistentCacheProviders = icCacheMap.map { (lib, cache) -> val persistentCacheProviders = icCacheMap.map { (lib, cache) ->
libraries[lib.toCanonicalPath()]!!.let { klib -> klib to createCacheProvider(cache) } libraries[lib.toCanonicalPath()]!! to createCacheProvider(cache)
}.toMap() }.toMap()
val nameToKotlinLibrary: Map<ModuleName, KotlinLibrary> = libraries.values.associateBy { it.moduleName } val nameToKotlinLibrary: Map<ModuleName, KotlinLibrary> = libraries.values.associateBy { it.moduleName }
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.ic.ICCache
import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData
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.lower.serialization.ir.JsIrModuleSerializer import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrModuleSerializer
@@ -396,7 +397,7 @@ fun prepareAnalyzedSourceModule(
analyzer: AbstractAnalyzerWithCompilerReport, analyzer: AbstractAnalyzerWithCompilerReport,
icUseGlobalSignatures: Boolean = false, icUseGlobalSignatures: Boolean = false,
icUseStdlibCache: Boolean = false, icUseStdlibCache: Boolean = false,
icCache: Map<String, SerializedIcData> = emptyMap(), icCache: Map<String, ICCache> = emptyMap(),
errorPolicy: ErrorTolerancePolicy = configuration.get(JSConfigurationKeys.ERROR_TOLERANCE_POLICY) ?: ErrorTolerancePolicy.DEFAULT, errorPolicy: ErrorTolerancePolicy = configuration.get(JSConfigurationKeys.ERROR_TOLERANCE_POLICY) ?: ErrorTolerancePolicy.DEFAULT,
): ModulesStructure { ): ModulesStructure {
val mainModule = MainModule.SourceFiles(files) val mainModule = MainModule.SourceFiles(files)
@@ -490,12 +491,12 @@ class ModulesStructure(
friendDependenciesPaths: Collection<String>, friendDependenciesPaths: Collection<String>,
val icUseGlobalSignatures: Boolean, val icUseGlobalSignatures: Boolean,
val icUseStdlibCache: Boolean, val icUseStdlibCache: Boolean,
val icCache: Map<String, SerializedIcData>, val icCache: Map<String, ICCache>,
) { ) {
val loweringsCacheProvider: LoweringsCacheProvider = when { val loweringsCacheProvider: LoweringsCacheProvider = when {
icUseStdlibCache -> object : LoweringsCacheProvider { icUseStdlibCache -> object : LoweringsCacheProvider {
override fun cacheByPath(path: String): SerializedIcData? { override fun cacheByPath(path: String): SerializedIcData? {
return icCache[path] return icCache[path]?.serializedIcData
} }
} }
icUseGlobalSignatures -> EmptyLoweringsCacheProvider icUseGlobalSignatures -> EmptyLoweringsCacheProvider
+1
View File
@@ -239,6 +239,7 @@ val testDataDir = project(":js:js.translator").projectDir.resolve("testData")
projectTest(parallel = true) { projectTest(parallel = true) {
setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true) setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true)
// systemProperty("kotlin.js.ir.pir", "false")
inputs.dir(rootDir.resolve("compiler/cli/cli-common/resources")) // compiler.xml inputs.dir(rootDir.resolve("compiler/cli/cli-common/resources")) // compiler.xml
@@ -19,7 +19,6 @@ import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.common.output.writeAllTo import org.jetbrains.kotlin.cli.common.output.writeAllTo
import org.jetbrains.kotlin.cli.js.resolve
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.config.*
@@ -27,7 +26,7 @@ import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.incremental.js.IncrementalDataProviderImpl import org.jetbrains.kotlin.incremental.js.IncrementalDataProviderImpl
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl
import org.jetbrains.kotlin.incremental.js.TranslationResultValue import org.jetbrains.kotlin.incremental.js.TranslationResultValue
import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData import org.jetbrains.kotlin.ir.backend.js.ic.ICCache
import org.jetbrains.kotlin.js.JavaScript import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
@@ -36,7 +35,10 @@ import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.dce.InputResource import org.jetbrains.kotlin.js.dce.InputResource
import org.jetbrains.kotlin.js.engine.loadFiles import org.jetbrains.kotlin.js.engine.loadFiles
import org.jetbrains.kotlin.js.facade.* import org.jetbrains.kotlin.js.facade.K2JSTranslator
import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.js.facade.TranslationResult
import org.jetbrains.kotlin.js.facade.TranslationUnit
import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapLocationRemapper import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapLocationRemapper
@@ -178,6 +180,8 @@ abstract class BasicBoxTest(
return dependenciesSymbols.toSet() + dependenciesSymbols.flatMap { modules[it]!!.allTransitiveDependencies() } return dependenciesSymbols.toSet() + dependenciesSymbols.flatMap { modules[it]!!.allTransitiveDependencies() }
} }
val checkIC = modules.any { it.value.hasFilesToRecompile }
val orderedModules = DFS.topologicalOrder(modules.values) { module -> module.dependenciesSymbols.mapNotNull { modules[it] } } val orderedModules = DFS.topologicalOrder(modules.values) { module -> module.dependenciesSymbols.mapNotNull { modules[it] } }
val testPackage = if (runPlainBoxFunction) null else testFactory.testPackage val testPackage = if (runPlainBoxFunction) null else testFactory.testPackage
@@ -191,7 +195,7 @@ abstract class BasicBoxTest(
} }
val testModuleName = if (runPlainBoxFunction) null else mainModuleName val testModuleName = if (runPlainBoxFunction) null else mainModuleName
val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"") val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"")
val icCache = mutableMapOf<String, SerializedIcData>() val icCache = mutableMapOf<String, ICCache>()
val generatedJsFiles = orderedModules.asReversed().mapNotNull { module -> val generatedJsFiles = orderedModules.asReversed().mapNotNull { module ->
val dependencies = module.dependenciesSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } val dependencies = module.dependenciesSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" }
@@ -233,6 +237,7 @@ abstract class BasicBoxTest(
safeExternalBooleanDiagnostic, safeExternalBooleanDiagnostic,
skipMangleVerification, skipMangleVerification,
abiVersion, abiVersion,
checkIC,
icCache icCache
) )
@@ -491,7 +496,8 @@ abstract class BasicBoxTest(
safeExternalBooleanDiagnostic: RuntimeDiagnostic?, safeExternalBooleanDiagnostic: RuntimeDiagnostic?,
skipMangleVerification: Boolean, skipMangleVerification: Boolean,
abiVersion: KotlinAbiVersion, abiVersion: KotlinAbiVersion,
icCache: MutableMap<String, SerializedIcData> checkIC: Boolean,
icCache: MutableMap<String, ICCache>
) { ) {
val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") }
val testFiles = kotlinFiles.map { it.fileName } val testFiles = kotlinFiles.map { it.fileName }
@@ -546,6 +552,8 @@ abstract class BasicBoxTest(
safeExternalBooleanDiagnostic, safeExternalBooleanDiagnostic,
skipMangleVerification, skipMangleVerification,
abiVersion, abiVersion,
checkIC,
recompile = false,
icCache icCache
) )
@@ -568,12 +576,13 @@ abstract class BasicBoxTest(
testPackage, testPackage,
testFunction, testFunction,
needsFullIrRuntime, needsFullIrRuntime,
expectActualLinker expectActualLinker,
icCache
) )
} }
} }
private fun checkIncrementalCompilation( protected open fun checkIncrementalCompilation(
sourceDirs: List<String>, sourceDirs: List<String>,
module: TestModule, module: TestModule,
kotlinFiles: List<TestFile>, kotlinFiles: List<TestFile>,
@@ -591,7 +600,8 @@ abstract class BasicBoxTest(
testPackage: String?, testPackage: String?,
testFunction: String, testFunction: String,
needsFullIrRuntime: Boolean, needsFullIrRuntime: Boolean,
expectActualLinker: Boolean expectActualLinker: Boolean,
icCaches: MutableMap<String, ICCache>
) { ) {
val sourceToTranslationUnit = hashMapOf<File, TranslationUnit>() val sourceToTranslationUnit = hashMapOf<File, TranslationUnit>()
for (testFile in kotlinFiles) { for (testFile in kotlinFiles) {
@@ -645,6 +655,8 @@ abstract class BasicBoxTest(
safeExternalBooleanDiagnostic = null, safeExternalBooleanDiagnostic = null,
skipMangleVerification = false, skipMangleVerification = false,
abiVersion = KotlinAbiVersion.CURRENT, abiVersion = KotlinAbiVersion.CURRENT,
incrementalCompilation = true,
recompile = true,
mutableMapOf() mutableMapOf()
) )
@@ -729,7 +741,9 @@ abstract class BasicBoxTest(
safeExternalBooleanDiagnostic: RuntimeDiagnostic?, safeExternalBooleanDiagnostic: RuntimeDiagnostic?,
skipMangleVerification: Boolean, skipMangleVerification: Boolean,
abiVersion: KotlinAbiVersion, abiVersion: KotlinAbiVersion,
icCache: MutableMap<String, SerializedIcData> incrementalCompilation: Boolean,
recompile: Boolean,
icCache: MutableMap<String, ICCache>
) { ) {
val translator = K2JSTranslator(config, false) val translator = K2JSTranslator(config, false)
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters)
@@ -866,7 +880,7 @@ abstract class BasicBoxTest(
private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile) private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile)
private fun createConfig( protected fun createConfig(
sourceDirs: List<String>, sourceDirs: List<String>,
module: TestModule, module: TestModule,
dependencies: List<String>, dependencies: List<String>,
@@ -1069,13 +1083,13 @@ abstract class BasicBoxTest(
} }
} }
private class TestFile(val fileName: String, val module: TestModule, val recompile: Boolean, val packageName: String) { protected class TestFile(val fileName: String, val module: TestModule, val recompile: Boolean, val packageName: String) {
init { init {
module.files += this module.files += this
} }
} }
private class TestModule( protected class TestModule(
name: String, name: String,
dependencies: List<String>, dependencies: List<String>,
friends: List<String>, friends: List<String>,
@@ -1147,7 +1161,7 @@ abstract class BasicBoxTest(
protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn")
const val TEST_MODULE = "JS_TESTS" const val TEST_MODULE = "JS_TESTS"
private const val DEFAULT_MODULE = "main" const val DEFAULT_MODULE = "main"
private const val TEST_FUNCTION = "box" private const val TEST_FUNCTION = "box"
private const val OLD_MODULE_SUFFIX = "-old" private const val OLD_MODULE_SUFFIX = "-old"
@@ -5,15 +5,16 @@
package org.jetbrains.kotlin.js.test package org.jetbrains.kotlin.js.test
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.backend.common.phaser.AnyNamedPhase import org.jetbrains.kotlin.backend.common.phaser.AnyNamedPhase
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.ic.prepareSingleLibraryIcCache
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
@@ -32,7 +33,7 @@ private val defaultRuntimeKlib = System.getProperty("kotlin.js.reduced.stdlib.pa
private val kotlinTestKLib = System.getProperty("kotlin.js.kotlin.test.path") private val kotlinTestKLib = System.getProperty("kotlin.js.kotlin.test.path")
// TODO Cache on FS (requires bootstrap) // TODO Cache on FS (requires bootstrap)
private val predefinedKlibHasIcCache = mutableMapOf<String, SerializedIcData?>( private val predefinedKlibHasIcCache = mutableMapOf<String, ICCache?>(
File(fullRuntimeKlib).absolutePath to null, File(fullRuntimeKlib).absolutePath to null,
File(kotlinTestKLib).absolutePath to null, File(kotlinTestKLib).absolutePath to null,
File(defaultRuntimeKlib).absolutePath to null File(defaultRuntimeKlib).absolutePath to null
@@ -75,7 +76,7 @@ abstract class BasicIrBoxTest(
val perModule: Boolean = getBoolean("kotlin.js.ir.perModule") val perModule: Boolean = getBoolean("kotlin.js.ir.perModule")
// TODO Design incremental compilation for IR and add test support // TODO Design incremental compilation for IR and add test support
override val incrementalCompilationChecksEnabled = false override val incrementalCompilationChecksEnabled = true
private val compilationCache = mutableMapOf<String, String>() private val compilationCache = mutableMapOf<String, String>()
@@ -111,9 +112,11 @@ abstract class BasicIrBoxTest(
safeExternalBooleanDiagnostic: RuntimeDiagnostic?, safeExternalBooleanDiagnostic: RuntimeDiagnostic?,
skipMangleVerification: Boolean, skipMangleVerification: Boolean,
abiVersion: KotlinAbiVersion, abiVersion: KotlinAbiVersion,
icCache: MutableMap<String, SerializedIcData> incrementalCompilation: Boolean,
recompile: Boolean,
icCache: MutableMap<String, ICCache>
) { ) {
val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } val filesToCompile = units.mapNotNull { (it as? TranslationUnit.SourceFile)?.file }
val runtimeKlibs = if (needsFullIrRuntime) listOf(fullRuntimeKlib, kotlinTestKLib) else listOf(defaultRuntimeKlib) val runtimeKlibs = if (needsFullIrRuntime) listOf(fullRuntimeKlib, kotlinTestKLib) else listOf(defaultRuntimeKlib)
@@ -123,11 +126,17 @@ abstract class BasicIrBoxTest(
compilationCache[it] ?: error("Can't find compiled module for dependency $it") compilationCache[it] ?: error("Can't find compiled module for dependency $it")
}).map { File(it).absolutePath }.toMutableList() }).map { File(it).absolutePath }.toMutableList()
val klibPath = outputFile.absolutePath.replace("_v5.js", "") val klibPath = outputFile.canonicalPath.replace("_v5.js", "")
prepareRuntimePirCaches(config, icCache) val actualOutputFile = outputFile.canonicalPath.let {
if (!isMainModule) klibPath else it
}
if (isMainModule && klibMainModule) { if (incrementalCompilationChecksEnabled && incrementalCompilation) {
runtimeKlibs.forEach { createIcCache(it, runtimeKlibs, config, icCache) }
}
if (!recompile) { // In case of incremental recompilation we only rebuild caches, not klib itself
val module = prepareAnalyzedSourceModule( val module = prepareAnalyzedSourceModule(
config.project, config.project,
filesToCompile, filesToCompile,
@@ -143,14 +152,16 @@ abstract class BasicIrBoxTest(
nopack = true, nopack = true,
jsOutputName = null jsOutputName = null
) )
allKlibPaths += File(klibPath).absolutePath
} }
val actualOutputFile = outputFile.absolutePath.let { val klibCannonPath = File(klibPath).canonicalPath
if (!isMainModule) klibPath else it
if (incrementalCompilation) {
icCache[klibCannonPath] = createIcCache(klibCannonPath, allKlibPaths + klibCannonPath, config, icCache)
} }
compilationCache[outputFile.name.replace(".js", ".meta.js")] = actualOutputFile
if (isMainModule) { if (isMainModule) {
logger.logFile("Output JS", outputFile) logger.logFile("Output JS", outputFile)
@@ -171,47 +182,27 @@ abstract class BasicIrBoxTest(
PhaseConfig(jsPhases) PhaseConfig(jsPhases)
} }
fun prepareModule(allowIc: Boolean): ModulesStructure { val module = ModulesStructure(
val useIc = runIcMode && allowIc config.project,
@Suppress("NAME_SHADOWING") MainModule.Klib(klibPath),
val icCache = if (useIc) icCache else emptyMap() config.configuration,
return if (!klibMainModule) { allKlibPaths + klibCannonPath,
prepareAnalyzedSourceModule( emptyList(),
config.project, icUseGlobalSignatures = runIcMode,
filesToCompile, icUseStdlibCache = runIcMode,
config.configuration, icCache = icCache
allKlibPaths, )
emptyList(),
AnalyzerWithCompilerReport(config.configuration),
icUseGlobalSignatures = useIc,
icUseStdlibCache = useIc,
icCache = icCache
)
} else {
ModulesStructure(
config.project,
MainModule.Klib(klibPath),
config.configuration,
allKlibPaths,
emptyList(),
icUseGlobalSignatures = useIc,
icUseStdlibCache = useIc,
icCache = icCache
)
}
}
if (!skipRegularMode) { if (!skipRegularMode) {
val module = prepareModule(true)
val irFactory = if (lowerPerModule) PersistentIrFactory() else IrFactoryImpl
val compiledModule = compile( val compiledModule = compile(
module, module,
phaseConfig = phaseConfig, phaseConfig = phaseConfig,
irFactory = irFactory, irFactory = IrFactoryImpl,
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
generateFullJs = true, generateFullJs = true,
generateDceJs = runIrDce, generateDceJs = runIrDce,
dceDriven = false,
es6mode = runEs6Mode, es6mode = runEs6Mode,
multiModule = splitPerModule || perModule, multiModule = splitPerModule || perModule,
propertyLazyInitialization = propertyLazyInitialization, propertyLazyInitialization = propertyLazyInitialization,
@@ -221,7 +212,10 @@ abstract class BasicIrBoxTest(
verifySignatures = !skipMangleVerification, verifySignatures = !skipMangleVerification,
) )
compiledModule.outputs!!.writeTo(outputFile, config) val jsOutputFile = if (recompile) File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
else outputFile
compiledModule.outputs!!.writeTo(jsOutputFile, config)
compiledModule.outputsAfterDce?.writeTo(dceOutputFile, config) compiledModule.outputsAfterDce?.writeTo(dceOutputFile, config)
@@ -232,54 +226,127 @@ abstract class BasicIrBoxTest(
} }
} }
if (runIrPir && !skipDceDriven) { if (runIrPir) {
val module = prepareModule(false) val compiledModule = compile(
compile(
module, module,
phaseConfig = phaseConfig, phaseConfig = phaseConfig,
irFactory = PersistentIrFactory(), irFactory = PersistentIrFactory(),
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
generateFullJs = true,
generateDceJs = runIrDce,
dceDriven = true, dceDriven = true,
es6mode = runEs6Mode, es6mode = runEs6Mode,
multiModule = splitPerModule || perModule, multiModule = splitPerModule || perModule,
propertyLazyInitialization = propertyLazyInitialization, propertyLazyInitialization = propertyLazyInitialization,
lowerPerModule = lowerPerModule,
safeExternalBoolean = safeExternalBoolean, safeExternalBoolean = safeExternalBoolean,
safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic,
verifySignatures = !skipMangleVerification verifySignatures = !skipMangleVerification,
).outputs!!.writeTo(pirOutputFile, config) )
compiledModule.outputs!!.writeTo(pirOutputFile, config)
} }
} else {
val module = prepareAnalyzedSourceModule(
config.project,
filesToCompile,
config.configuration,
allKlibPaths,
emptyList(),
AnalyzerWithCompilerReport(config.configuration)
)
generateKLib(
module,
irFactory = IrFactoryImpl,
outputKlibPath = actualOutputFile,
nopack = true,
verifySignatures = !skipMangleVerification,
abiVersion = abiVersion,
null
)
if (runIcMode) {
icCache[actualOutputFile] = createPirCache(actualOutputFile, allKlibPaths + actualOutputFile, config, icCache)
}
logger.logFile("Output klib", File(actualOutputFile))
compilationCache[outputFile.name.replace(".js", ".meta.js")] = actualOutputFile
} }
} }
override fun checkIncrementalCompilation(
sourceDirs: List<String>,
module: TestModule,
kotlinFiles: List<TestFile>,
dependencies: List<String>,
allDependencies: List<String>,
friends: List<String>,
multiModule: Boolean,
tmpDir: File,
remap: Boolean,
outputFile: File,
outputPrefixFile: File?,
outputPostfixFile: File?,
mainCallParameters: MainCallParameters,
incrementalData: IncrementalData,
testPackage: String?,
testFunction: String,
needsFullIrRuntime: Boolean,
expectActualLinker: Boolean,
icCaches: MutableMap<String, ICCache>
) {
val isMainModule = module.name == DEFAULT_MODULE || module.name == TEST_MODULE
val cacheKey = outputFile.canonicalPath.replace("_v5.js", "")
private fun createPirCache(path: String, allKlibPaths: Collection<String>, config: JsConfig, icCache: Map<String, SerializedIcData>): SerializedIcData { val icCache = icCaches[cacheKey] ?: error("No IC data found for module ${module.name}")
val dirtyFiles = mutableListOf<String>()
val oldBinaryAsts = mutableMapOf<String, ByteArray>()
for (testFile in kotlinFiles) {
if (testFile.recompile) {
val fileName = testFile.fileName
oldBinaryAsts[fileName] = icCache.dataProvider.binaryAst(fileName)
icCache.dataConsumer.invalidateForFile(fileName)
dirtyFiles.add(fileName)
}
}
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
val recompiledMinOutputFile = File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + "-min.js")
val recompiledPirOutputFile = File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + "-pir.js")
val recompiledConfig = createConfig(
sourceDirs,
module,
dependencies,
allDependencies,
friends,
multiModule,
tmpDir,
null,
expectActualLinker,
ErrorTolerancePolicy.DEFAULT
)
translateFiles(
dirtyFiles.map { TranslationUnit.SourceFile(createPsiFile(it)) },
outputFile,
recompiledMinOutputFile,
recompiledPirOutputFile,
recompiledConfig,
outputPrefixFile,
outputPostfixFile,
mainCallParameters,
incrementalData,
remap,
testPackage,
testFunction,
needsFullIrRuntime,
isMainModule,
skipDceDriven = true,
splitPerModule = false, // TODO??
propertyLazyInitialization = false, // ??
safeExternalBoolean = false,
safeExternalBooleanDiagnostic = null,
skipMangleVerification = false,
KotlinAbiVersion.CURRENT,
incrementalCompilation = true,
recompile = true,
icCaches
)
val newBinaryAsts = dirtyFiles.associateWith { icCache.dataProvider.binaryAst(it) }
for (file in dirtyFiles) {
val oldBinaryAst = oldBinaryAsts[file]
val newBinaryAst = newBinaryAsts[file]
assert(oldBinaryAst.contentEquals(newBinaryAst)) { "Binary AST changed after recompilation for file $file" }
}
if (isMainModule) {
val originalOutput = FileUtil.loadFile(outputFile)
val recompiledOutput = FileUtil.loadFile(recompiledOutputFile)
assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput)
}
}
private fun createPirCache(path: String, allKlibPaths: Collection<String>, config: JsConfig, icCache: Map<String, ICCache>): ICCache {
val icData = predefinedKlibHasIcCache[path] ?: prepareSingleLibraryIcCache( val icData = predefinedKlibHasIcCache[path] ?: prepareSingleLibraryIcCache(
project = project, project = project,
configuration = config.configuration, configuration = config.configuration,
@@ -295,7 +362,23 @@ abstract class BasicIrBoxTest(
return icData return icData
} }
private fun prepareRuntimePirCaches(config: JsConfig, icCache: MutableMap<String, SerializedIcData>) { @Suppress("UNUSED_PARAMETER")
private fun createIcCache(path: String, allKlibPaths: Collection<String>, config: JsConfig, icCache: Map<String, ICCache>): ICCache {
fun prepare(): ICCache {
return ICCache(PersistentCacheProvider.EMPTY, PersistentCacheConsumer.EMPTY, SerializedIcData(emptyList()))
}
val icData = predefinedKlibHasIcCache[path] ?: prepare()
if (path in predefinedKlibHasIcCache) {
predefinedKlibHasIcCache[path] = icData
}
return icData
}
private fun prepareRuntimePirCaches(config: JsConfig, icCache: MutableMap<String, ICCache>) {
if (!runIcMode) return if (!runIcMode) return
val defaultRuntimePath = File(defaultRuntimeKlib).canonicalPath val defaultRuntimePath = File(defaultRuntimeKlib).canonicalPath