diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 034c65393b7..ffffc4602bc 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -38,9 +38,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCacheForModule -import org.jetbrains.kotlin.ir.backend.js.ic.buildCache -import org.jetbrains.kotlin.ir.backend.js.ic.checkCaches +import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult @@ -76,6 +74,10 @@ class K2JsIrCompiler : CLICompiler() { @Suppress("UNUSED_PARAMETER") private fun usePerFileInvalidator(configuration: CompilerConfiguration): Boolean = false + private fun IcCacheInfo.toICCacheMap(): Map { + return data.map { it.key to ICCache(PersistentCacheProvider.EMPTY, PersistentCacheConsumer.EMPTY, it.value) }.toMap() + } + override fun doExecute( arguments: K2JSCompilerArguments, configuration: CompilerConfiguration, @@ -234,7 +236,8 @@ class K2JsIrCompiler : CLICompiler() { // Run analysis if main module is sources lateinit var sourceModule: ModulesStructure - if (arguments.includes == null) { + val includes = arguments.includes + if (includes == null) { do { sourceModule = prepareAnalyzedSourceModule( projectJs, @@ -245,7 +248,7 @@ class K2JsIrCompiler : CLICompiler() { AnalyzerWithCompilerReport(config.configuration), icUseGlobalSignatures = 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 if (result is JsAnalysisResult.RetryWithAdditionalRoots) { @@ -276,8 +279,6 @@ class K2JsIrCompiler : CLICompiler() { val phaseConfig = createPhaseConfig(jsPhases, arguments, messageCollector) - val includes = arguments.includes - val module = if (includes != null) { if (sourcesFiles.isNotEmpty()) { messageCollector.report(ERROR, "Source files are not supported when -Xinclude is present") @@ -294,7 +295,7 @@ class K2JsIrCompiler : CLICompiler() { friendLibraries, icUseGlobalSignatures = 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 { sourceModule diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/fileUtil.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/fileUtil.kt index b1ad4bb631f..7517b9f83f7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/fileUtil.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/fileUtil.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.ir.backend.js.ic import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.analyzer.AbstractAnalyzerWithCompilerReport import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.MainModule 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) private val compilerVersion = Random.nextULong() +private fun IcCacheInfo.toICCacheMap(): Map { + return data.map { it.key to ICCache(PersistentCacheProvider.EMPTY, PersistentCacheConsumer.EMPTY, it.value) }.toMap() +} + // TODO more parameters for lowerings // Returns true if caches were built. False if caches were up-to-date. fun buildCache( @@ -50,9 +53,9 @@ fun buildCache( File(icDir, "info").delete() 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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ic.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ic.kt index bbe7701b798..5cdb367416f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ic.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ic.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.ir.backend.js.ic import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.analyzer.AbstractAnalyzerWithCompilerReport import org.jetbrains.kotlin.backend.common.lower import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.* @@ -34,8 +33,8 @@ fun prepareSingleLibraryIcCache( dependencies: Collection, friendDependencies: Collection = emptyList(), exportedDeclarations: Set = emptySet(), - icCache: Map = emptyMap(), -): SerializedIcData { + icCache: Map = emptyMap(), +): ICCache { val irFactory = PersistentIrFactory() val controller = WholeWorldStageController() irFactory.stageController = controller @@ -67,13 +66,17 @@ fun prepareSingleLibraryIcCache( lowerPreservingIcData(moduleFragment, context, controller) - return IcSerializer( - context.irBuiltIns, - context.mapping, - irFactory, - deserializer, - moduleFragment - ).serializeDeclarations(irFactory.allDeclarations) + return ICCache( + PersistentCacheProvider.EMPTY, + PersistentCacheConsumer.EMPTY, + IcSerializer( + context.irBuiltIns, + context.mapping, + irFactory, + deserializer, + moduleFragment + ).serializeDeclarations(irFactory.allDeclarations) + ) } private fun KotlinResolvedLibrary.allDependencies(): List { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/invalidation.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/invalidation.kt index 7d8ecd18d19..11a868bfd26 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/invalidation.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/invalidation.kt @@ -265,7 +265,8 @@ private fun createCacheConsumer(path: String): PersistentCacheConsumer { private fun loadCacheInfo(cachePaths: Collection): MutableMap { 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 + val result = mutableMapOf() + return caches.associateTo(result) { it.libPath.toCanonicalPath() to it.path } } private fun loadLibraries(configuration: CompilerConfiguration, dependencies: Collection): Map { @@ -298,7 +299,7 @@ fun actualizeCacheForModule( val libraries: Map = loadLibraries(compilerConfiguration, dependencies) val persistentCacheProviders = icCacheMap.map { (lib, cache) -> - libraries[lib.toCanonicalPath()]!!.let { klib -> klib to createCacheProvider(cache) } + libraries[lib.toCanonicalPath()]!! to createCacheProvider(cache) }.toMap() val nameToKotlinLibrary: Map = libraries.values.associateBy { it.moduleName } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt index 1dac0d3674b..b437742cc72 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.ir.IrBuiltIns 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.lower.serialization.ir.JsIrLinker import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrModuleSerializer @@ -396,7 +397,7 @@ fun prepareAnalyzedSourceModule( analyzer: AbstractAnalyzerWithCompilerReport, icUseGlobalSignatures: Boolean = false, icUseStdlibCache: Boolean = false, - icCache: Map = emptyMap(), + icCache: Map = emptyMap(), errorPolicy: ErrorTolerancePolicy = configuration.get(JSConfigurationKeys.ERROR_TOLERANCE_POLICY) ?: ErrorTolerancePolicy.DEFAULT, ): ModulesStructure { val mainModule = MainModule.SourceFiles(files) @@ -490,12 +491,12 @@ class ModulesStructure( friendDependenciesPaths: Collection, val icUseGlobalSignatures: Boolean, val icUseStdlibCache: Boolean, - val icCache: Map, + val icCache: Map, ) { val loweringsCacheProvider: LoweringsCacheProvider = when { icUseStdlibCache -> object : LoweringsCacheProvider { override fun cacheByPath(path: String): SerializedIcData? { - return icCache[path] + return icCache[path]?.serializedIcData } } icUseGlobalSignatures -> EmptyLoweringsCacheProvider diff --git a/js/js.tests/build.gradle.kts b/js/js.tests/build.gradle.kts index a4c8064b591..fa94b68cec5 100644 --- a/js/js.tests/build.gradle.kts +++ b/js/js.tests/build.gradle.kts @@ -239,6 +239,7 @@ val testDataDir = project(":js:js.translator").projectDir.resolve("testData") projectTest(parallel = true) { setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true) +// systemProperty("kotlin.js.ir.pir", "false") inputs.dir(rootDir.resolve("compiler/cli/cli-common/resources")) // compiler.xml diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 35e7a07f413..ce237275470 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -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.PrintingMessageCollector 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.KotlinCoreEnvironment 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.IncrementalResultsConsumerImpl 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.backend.JsToStringGenerationVisitor 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.InputResource 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.sourcemaps.SourceMapError import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapLocationRemapper @@ -178,6 +180,8 @@ abstract class BasicBoxTest( 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 testPackage = if (runPlainBoxFunction) null else testFactory.testPackage @@ -191,7 +195,7 @@ abstract class BasicBoxTest( } val testModuleName = if (runPlainBoxFunction) null else mainModuleName val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"") - val icCache = mutableMapOf() + val icCache = mutableMapOf() val generatedJsFiles = orderedModules.asReversed().mapNotNull { module -> val dependencies = module.dependenciesSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } @@ -233,6 +237,7 @@ abstract class BasicBoxTest( safeExternalBooleanDiagnostic, skipMangleVerification, abiVersion, + checkIC, icCache ) @@ -491,7 +496,8 @@ abstract class BasicBoxTest( safeExternalBooleanDiagnostic: RuntimeDiagnostic?, skipMangleVerification: Boolean, abiVersion: KotlinAbiVersion, - icCache: MutableMap + checkIC: Boolean, + icCache: MutableMap ) { val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val testFiles = kotlinFiles.map { it.fileName } @@ -546,6 +552,8 @@ abstract class BasicBoxTest( safeExternalBooleanDiagnostic, skipMangleVerification, abiVersion, + checkIC, + recompile = false, icCache ) @@ -568,12 +576,13 @@ abstract class BasicBoxTest( testPackage, testFunction, needsFullIrRuntime, - expectActualLinker + expectActualLinker, + icCache ) } } - private fun checkIncrementalCompilation( + protected open fun checkIncrementalCompilation( sourceDirs: List, module: TestModule, kotlinFiles: List, @@ -591,7 +600,8 @@ abstract class BasicBoxTest( testPackage: String?, testFunction: String, needsFullIrRuntime: Boolean, - expectActualLinker: Boolean + expectActualLinker: Boolean, + icCaches: MutableMap ) { val sourceToTranslationUnit = hashMapOf() for (testFile in kotlinFiles) { @@ -645,6 +655,8 @@ abstract class BasicBoxTest( safeExternalBooleanDiagnostic = null, skipMangleVerification = false, abiVersion = KotlinAbiVersion.CURRENT, + incrementalCompilation = true, + recompile = true, mutableMapOf() ) @@ -729,7 +741,9 @@ abstract class BasicBoxTest( safeExternalBooleanDiagnostic: RuntimeDiagnostic?, skipMangleVerification: Boolean, abiVersion: KotlinAbiVersion, - icCache: MutableMap + incrementalCompilation: Boolean, + recompile: Boolean, + icCache: MutableMap ) { val translator = K2JSTranslator(config, false) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) @@ -866,7 +880,7 @@ abstract class BasicBoxTest( private fun createPsiFiles(fileNames: List): List = fileNames.map(this::createPsiFile) - private fun createConfig( + protected fun createConfig( sourceDirs: List, module: TestModule, dependencies: List, @@ -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 { module.files += this } } - private class TestModule( + protected class TestModule( name: String, dependencies: List, friends: List, @@ -1147,7 +1161,7 @@ abstract class BasicBoxTest( protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") 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 OLD_MODULE_SUFFIX = "-old" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 588c5e9fbd1..ec8b7cedf85 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -5,15 +5,16 @@ 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.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData -import org.jetbrains.kotlin.ir.backend.js.ic.prepareSingleLibraryIcCache +import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl 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.JsConfig 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") // TODO Cache on FS (requires bootstrap) -private val predefinedKlibHasIcCache = mutableMapOf( +private val predefinedKlibHasIcCache = mutableMapOf( File(fullRuntimeKlib).absolutePath to null, File(kotlinTestKLib).absolutePath to null, File(defaultRuntimeKlib).absolutePath to null @@ -75,7 +76,7 @@ abstract class BasicIrBoxTest( val perModule: Boolean = getBoolean("kotlin.js.ir.perModule") // TODO Design incremental compilation for IR and add test support - override val incrementalCompilationChecksEnabled = false + override val incrementalCompilationChecksEnabled = true private val compilationCache = mutableMapOf() @@ -111,9 +112,11 @@ abstract class BasicIrBoxTest( safeExternalBooleanDiagnostic: RuntimeDiagnostic?, skipMangleVerification: Boolean, abiVersion: KotlinAbiVersion, - icCache: MutableMap + incrementalCompilation: Boolean, + recompile: Boolean, + icCache: MutableMap ) { - 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) @@ -123,11 +126,17 @@ abstract class BasicIrBoxTest( compilationCache[it] ?: error("Can't find compiled module for dependency $it") }).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( config.project, filesToCompile, @@ -143,14 +152,16 @@ abstract class BasicIrBoxTest( nopack = true, jsOutputName = null ) - - allKlibPaths += File(klibPath).absolutePath } - val actualOutputFile = outputFile.absolutePath.let { - if (!isMainModule) klibPath else it + val klibCannonPath = File(klibPath).canonicalPath + + if (incrementalCompilation) { + icCache[klibCannonPath] = createIcCache(klibCannonPath, allKlibPaths + klibCannonPath, config, icCache) } + compilationCache[outputFile.name.replace(".js", ".meta.js")] = actualOutputFile + if (isMainModule) { logger.logFile("Output JS", outputFile) @@ -171,47 +182,27 @@ abstract class BasicIrBoxTest( PhaseConfig(jsPhases) } - fun prepareModule(allowIc: Boolean): ModulesStructure { - val useIc = runIcMode && allowIc - @Suppress("NAME_SHADOWING") - val icCache = if (useIc) icCache else emptyMap() - return if (!klibMainModule) { - prepareAnalyzedSourceModule( - config.project, - filesToCompile, - config.configuration, - 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 - ) - } - } + val module = ModulesStructure( + config.project, + MainModule.Klib(klibPath), + config.configuration, + allKlibPaths + klibCannonPath, + emptyList(), + icUseGlobalSignatures = runIcMode, + icUseStdlibCache = runIcMode, + icCache = icCache + ) if (!skipRegularMode) { - val module = prepareModule(true) - val irFactory = if (lowerPerModule) PersistentIrFactory() else IrFactoryImpl val compiledModule = compile( module, phaseConfig = phaseConfig, - irFactory = irFactory, + irFactory = IrFactoryImpl, mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), generateFullJs = true, generateDceJs = runIrDce, + dceDriven = false, es6mode = runEs6Mode, multiModule = splitPerModule || perModule, propertyLazyInitialization = propertyLazyInitialization, @@ -221,7 +212,10 @@ abstract class BasicIrBoxTest( 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) @@ -232,54 +226,127 @@ abstract class BasicIrBoxTest( } } - if (runIrPir && !skipDceDriven) { - val module = prepareModule(false) - compile( + if (runIrPir) { + val compiledModule = compile( module, phaseConfig = phaseConfig, irFactory = PersistentIrFactory(), mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), + generateFullJs = true, + generateDceJs = runIrDce, dceDriven = true, es6mode = runEs6Mode, multiModule = splitPerModule || perModule, propertyLazyInitialization = propertyLazyInitialization, + lowerPerModule = lowerPerModule, safeExternalBoolean = safeExternalBoolean, safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, - verifySignatures = !skipMangleVerification - ).outputs!!.writeTo(pirOutputFile, config) + verifySignatures = !skipMangleVerification, + ) + 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, + module: TestModule, + kotlinFiles: List, + dependencies: List, + allDependencies: List, + friends: List, + 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 + ) { + val isMainModule = module.name == DEFAULT_MODULE || module.name == TEST_MODULE + val cacheKey = outputFile.canonicalPath.replace("_v5.js", "") - private fun createPirCache(path: String, allKlibPaths: Collection, config: JsConfig, icCache: Map): SerializedIcData { + val icCache = icCaches[cacheKey] ?: error("No IC data found for module ${module.name}") + + val dirtyFiles = mutableListOf() + val oldBinaryAsts = mutableMapOf() + + 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, config: JsConfig, icCache: Map): ICCache { val icData = predefinedKlibHasIcCache[path] ?: prepareSingleLibraryIcCache( project = project, configuration = config.configuration, @@ -295,7 +362,23 @@ abstract class BasicIrBoxTest( return icData } - private fun prepareRuntimePirCaches(config: JsConfig, icCache: MutableMap) { + @Suppress("UNUSED_PARAMETER") + private fun createIcCache(path: String, allKlibPaths: Collection, config: JsConfig, icCache: Map): 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) { if (!runIcMode) return val defaultRuntimePath = File(defaultRuntimeKlib).canonicalPath