[JS IR IC] Do not read non modified JS into memory

Optimizaion: instead of reading all non modified JS files
 from the cache into the memory at once, the patch allows
 copying non-modified JS files from disk one by one
 during the compilation output writing routine.
This commit is contained in:
Alexander Korepanov
2022-12-20 15:41:27 +01:00
committed by Space Team
parent dd268d67ff
commit 75931fb9c8
10 changed files with 163 additions and 118 deletions
@@ -77,6 +77,7 @@ import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerIr
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsCodeGenerator import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsCodeGenerator
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputsBuilt
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol
@@ -155,7 +156,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
return transformer.makeJsCodeGenerator(ir.allModules, mode) return transformer.makeJsCodeGenerator(ir.allModules, mode)
} }
fun compileAndTransformIrNew(): CompilationOutputs { fun compileAndTransformIrNew(): CompilationOutputsBuilt {
return makeJsCodeGenerator().generateJsCode(relativeRequirePath = true, outJsProgram = false) return makeJsCodeGenerator().generateJsCode(relativeRequirePath = true, outJsProgram = false)
} }
} }
@@ -317,7 +318,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
) )
val (outputs, rebuiltModules) = jsExecutableProducer.buildExecutable(arguments.irPerModule, outJsProgram = false) val (outputs, rebuiltModules) = jsExecutableProducer.buildExecutable(arguments.irPerModule, outJsProgram = false)
outputs.write(outputDir, outputName, arguments.generateDts, moduleName, moduleKind) outputs.writeAll(outputDir, outputName, arguments.generateDts, moduleName, moduleKind)
messageCollector.report(INFO, "Executable production duration (IC): ${System.currentTimeMillis() - beforeIc2Js}ms") messageCollector.report(INFO, "Executable production duration (IC): ${System.currentTimeMillis() - beforeIc2Js}ms")
for ((event, duration) in jsExecutableProducer.getStopwatchLaps()) { for ((event, duration) in jsExecutableProducer.getStopwatchLaps()) {
@@ -393,7 +394,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
messageCollector.report(INFO, "Executable production duration: ${System.currentTimeMillis() - start}ms") messageCollector.report(INFO, "Executable production duration: ${System.currentTimeMillis() - start}ms")
outputs.write(outputDir, outputName, arguments.generateDts, moduleName, moduleKind) outputs.writeAll(outputDir, outputName, arguments.generateDts, moduleName, moduleKind)
} catch (e: CompilationException) { } catch (e: CompilationException) {
messageCollector.report( messageCollector.report(
ERROR, ERROR,
@@ -704,28 +705,6 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
return icCaches return icCaches
} }
private fun CompilationOutputs.write(outputDir: File, outputName: String, genDTS: Boolean, moduleName: String, moduleKind: ModuleKind) {
val outputFile = outputDir.resolve("$outputName.js")
outputFile.parentFile.mkdirs()
outputFile.write(this)
dependencies.forEach { (name, content) ->
outputDir.resolve("$name.js").let {
it.parentFile.mkdirs()
it.write(content)
}
}
if (genDTS) {
val dtsFile = outputDir.resolve("$outputName.d.ts")
dtsFile.writeText(getFullTsDefinition(moduleName, moduleKind))
}
}
private fun File.write(outputs: CompilationOutputs) {
writeText(outputs.jsCode)
outputs.writeSourceMapIfPresent(this)
}
override fun setupPlatformSpecificArgumentsAndServices( override fun setupPlatformSpecificArgumentsAndServices(
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
arguments: K2JSCompilerArguments, arguments: K2JSCompilerArguments,
@@ -11,46 +11,24 @@ import org.jetbrains.kotlin.backend.common.serialization.linkerissues.checkNoUnb
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.ir.backend.js.lower.collectNativeImplementations 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.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.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrFactory 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.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.RuntimeDiagnostic import org.jetbrains.kotlin.js.config.RuntimeDiagnostic
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import java.io.File
class CompilerResult( class CompilerResult(
val outputs: Map<TranslationMode, CompilationOutputs>, val outputs: Map<TranslationMode, CompilationOutputs>,
) )
class CompilationOutputs(
val jsCode: String,
val tsDefinitions: TypeScriptFragment? = null,
val jsProgram: JsProgram? = null,
val sourceMap: String? = null,
val dependencies: Iterable<Pair<String, CompilationOutputs>> = emptyList()
) {
fun addDependencies(depends: Iterable<Pair<String, CompilationOutputs>>): CompilationOutputs {
return CompilationOutputs(jsCode, tsDefinitions, jsProgram, sourceMap, depends)
}
fun getFullTsDefinition(name: String, moduleKind: ModuleKind): String {
val allTsDefinitions = dependencies.mapNotNull { it.second.tsDefinitions } + listOfNotNull(tsDefinitions)
return allTsDefinitions.toTypeScript(name, moduleKind)
}
}
class LoweredIr( class LoweredIr(
val context: JsIrBackendContext, val context: JsIrBackendContext,
val mainModule: IrModuleFragment, val mainModule: IrModuleFragment,
@@ -163,11 +141,3 @@ fun compileIr(
return LoweredIr(context, moduleFragment, allModules, moduleToName) return LoweredIr(context, moduleFragment, allModules, moduleToName)
} }
fun CompilationOutputs.writeSourceMapIfPresent(outputJsFile: File) {
sourceMap?.let {
val mapFile = outputJsFile.resolveSibling("${outputJsFile.name}.map")
outputJsFile.appendText("\n//# sourceMappingURL=${mapFile.name}\n")
mapFile.writeText(it)
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.ir.backend.js.ic package org.jetbrains.kotlin.ir.backend.js.ic
import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.* import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
@@ -96,11 +95,10 @@ class JsExecutableProducer(
val mainModule = cachedMainModule.compileModule(mainModuleName, true) val mainModule = cachedMainModule.compileModule(mainModuleName, true)
val cachedOtherModules = cachedProgram.dropLast(1) val cachedOtherModules = cachedProgram.dropLast(1)
val dependencies = cachedOtherModules.map { mainModule.dependencies = cachedOtherModules.map {
it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false) it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false)
} }
stopwatch.stop() stopwatch.stop()
val compilationOut = mainModule.addDependencies(dependencies) return BuildResult(mainModule, rebuildModules)
return BuildResult(compilationOut, rebuildModules)
} }
} }
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.ir.backend.js.ic 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.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CrossModuleReferences
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModuleHeader
import java.io.File import java.io.File
class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) { class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
@@ -99,17 +96,19 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
} }
fun fetchCompiledJsCode(artifact: ModuleArtifact) = artifact.artifactsDir?.let { cacheDir -> fun fetchCompiledJsCode(artifact: ModuleArtifact) = artifact.artifactsDir?.let { cacheDir ->
val jsCode = File(cacheDir, CACHED_MODULE_JS).ifExists { readText() } val jsCodeFilePath = File(cacheDir, CACHED_MODULE_JS).ifExists { absolutePath }
val sourceMap = File(cacheDir, CACHED_MODULE_JS_MAP).ifExists { readText() } val sourceMapFilePath = File(cacheDir, CACHED_MODULE_JS_MAP).ifExists { absolutePath }
val tsDefinitions = File(cacheDir, CACHED_MODULE_D_TS).ifExists { TypeScriptFragment(readText()) } val tsDefinitionsFilePath = File(cacheDir, CACHED_MODULE_D_TS).ifExists { absolutePath }
jsCode?.let { CompilationOutputs(it, tsDefinitions, null, sourceMap) } jsCodeFilePath?.let { CompilationOutputsCached(it, sourceMapFilePath, tsDefinitionsFilePath) }
} }
fun commitCompiledJsCode(artifact: ModuleArtifact, compilationOutputs: CompilationOutputs) = artifact.artifactsDir?.let { cacheDir -> fun commitCompiledJsCode(artifact: ModuleArtifact, compilationOutputs: CompilationOutputsBuilt) =
File(cacheDir, CACHED_MODULE_JS).writeIfNotNull(compilationOutputs.jsCode) artifact.artifactsDir?.let { cacheDir ->
File(cacheDir, CACHED_MODULE_JS_MAP).writeIfNotNull(compilationOutputs.sourceMap) val jsCodeFile = File(cacheDir, CACHED_MODULE_JS)
File(cacheDir, CACHED_MODULE_D_TS).writeIfNotNull(compilationOutputs.tsDefinitions?.raw) val jsMapFile = File(cacheDir, CACHED_MODULE_JS_MAP)
} compilationOutputs.writeJsCode(jsCodeFile, jsMapFile)
File(cacheDir, CACHED_MODULE_D_TS).writeIfNotNull(compilationOutputs.tsDefinitions?.raw)
}
fun loadProgramHeadersFromCache(): List<CachedModuleInfo> { fun loadProgramHeadersFromCache(): List<CachedModuleInfo> {
return moduleArtifacts.map { artifact -> return moduleArtifacts.map { artifact ->
@@ -0,0 +1,95 @@
/*
* 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.transformers.irToJs
import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.io.File
abstract class CompilationOutputs {
var dependencies: Collection<Pair<String, CompilationOutputs>> = emptyList()
abstract val tsDefinitions: TypeScriptFragment?
abstract val jsProgram: JsProgram?
abstract fun writeJsCode(outputJsFile: File, outputJsMapFile: File)
fun writeAll(outputDir: File, outputName: String, genDTS: Boolean, moduleName: String, moduleKind: ModuleKind): List<String> {
val writtenJsFiles = ArrayList<String>(dependencies.size + 1)
val outputJsFile = outputDir.resolve("$outputName.js")
outputJsFile.parentFile.mkdirs()
writeJsCode(outputJsFile, outputJsFile.mapForJsFile)
dependencies.forEach { (name, content) ->
outputDir.resolve("$name.js").let { depJsFile ->
depJsFile.parentFile.mkdirs()
content.writeJsCode(depJsFile, depJsFile.mapForJsFile)
writtenJsFiles += depJsFile.absolutePath
}
}
writtenJsFiles += outputJsFile.absolutePath
if (genDTS) {
outputJsFile.dtsForJsFile.writeText(getFullTsDefinition(moduleName, moduleKind))
}
return writtenJsFiles
}
fun getFullTsDefinition(moduleName: String, moduleKind: ModuleKind): String {
val allTsDefinitions = dependencies.mapNotNull { it.second.tsDefinitions } + listOfNotNull(tsDefinitions)
return allTsDefinitions.toTypeScript(moduleName, moduleKind)
}
private val File.mapForJsFile
get() = resolveSibling("$name.map")
private val File.dtsForJsFile
get() = resolveSibling("$nameWithoutExtension.d.ts")
}
class CompilationOutputsBuilt(
private val rawJsCode: String,
private val sourceMap: String?,
override val tsDefinitions: TypeScriptFragment?,
override val jsProgram: JsProgram?,
) : CompilationOutputs() {
override fun writeJsCode(outputJsFile: File, outputJsMapFile: File) {
var jsCodeWithSourceMap = rawJsCode
sourceMap?.let {
outputJsMapFile.writeText(it)
jsCodeWithSourceMap = "$jsCodeWithSourceMap\n//# sourceMappingURL=${outputJsMapFile.name}\n"
}
outputJsFile.writeText(jsCodeWithSourceMap)
}
}
class CompilationOutputsCached(
private val jsCodeFilePath: String,
private val sourceMapFilePath: String?,
private val tsDefinitionsFilePath: String?
) : CompilationOutputs() {
override val tsDefinitions: TypeScriptFragment?
get() = tsDefinitionsFilePath?.let { TypeScriptFragment(File(it).readText()) }
override val jsProgram: JsProgram?
get() = null
override fun writeJsCode(outputJsFile: File, outputJsMapFile: File) {
File(jsCodeFilePath).copyTo(outputJsFile, true)
sourceMapFilePath?.let {
File(it).copyTo(outputJsMapFile, true)
}
}
}
@@ -81,7 +81,7 @@ class JsCodeGenerator(
private val moduleKind: ModuleKind, private val moduleKind: ModuleKind,
private val sourceMapsInfo: SourceMapsInfo? private val sourceMapsInfo: SourceMapsInfo?
) { ) {
fun generateJsCode(relativeRequirePath: Boolean, outJsProgram: Boolean): CompilationOutputs { fun generateJsCode(relativeRequirePath: Boolean, outJsProgram: Boolean): CompilationOutputsBuilt {
return generateWrappedModuleBody( return generateWrappedModuleBody(
multiModule, multiModule,
mainModuleName, mainModuleName,
@@ -380,7 +380,7 @@ private fun generateWrappedModuleBody(
sourceMapsInfo: SourceMapsInfo?, sourceMapsInfo: SourceMapsInfo?,
relativeRequirePath: Boolean, relativeRequirePath: Boolean,
outJsProgram: Boolean outJsProgram: Boolean
): CompilationOutputs { ): CompilationOutputsBuilt {
if (multiModule) { if (multiModule) {
// mutable container allows explicitly remove elements from itself, // mutable container allows explicitly remove elements from itself,
// so we are able to help GC to free heavy JsIrModule objects // so we are able to help GC to free heavy JsIrModule objects
@@ -398,7 +398,7 @@ private fun generateWrappedModuleBody(
) )
} }
val dependencies = buildList(moduleToRef.size) { mainModule.dependencies = buildList(moduleToRef.size) {
while (moduleToRef.isNotEmpty()) { while (moduleToRef.isNotEmpty()) {
moduleToRef.removeFirst().let { (module, moduleRef) -> moduleToRef.removeFirst().let { (module, moduleRef) ->
val moduleName = module.externalModuleName val moduleName = module.externalModuleName
@@ -416,7 +416,7 @@ private fun generateWrappedModuleBody(
} }
} }
return mainModule.addDependencies(dependencies) return mainModule
} else { } else {
return generateSingleWrappedModuleBody( return generateSingleWrappedModuleBody(
mainModuleName, mainModuleName,
@@ -437,7 +437,7 @@ fun generateSingleWrappedModuleBody(
generateCallToMain: Boolean, generateCallToMain: Boolean,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty(moduleKind), crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty(moduleKind),
outJsProgram: Boolean = true outJsProgram: Boolean = true
): CompilationOutputs { ): CompilationOutputsBuilt {
val program = Merger( val program = Merger(
moduleName, moduleName,
moduleKind, moduleKind,
@@ -476,10 +476,10 @@ fun generateSingleWrappedModuleBody(
program.accept(JsToStringGenerationVisitor(jsCode, sourceMapBuilderConsumer)) program.accept(JsToStringGenerationVisitor(jsCode, sourceMapBuilderConsumer))
return CompilationOutputs( return CompilationOutputsBuilt(
jsCode.toString(), jsCode.toString(),
sourceMapBuilder?.build(),
fragments.mapNotNull { it.dts }.ifNotEmpty { joinTypeScriptFragments() }, fragments.mapNotNull { it.dts }.ifNotEmpty { joinTypeScriptFragments() },
program.takeIf { outJsProgram }, program.takeIf { outJsProgram }
sourceMapBuilder?.build()
) )
} }
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
@@ -225,16 +226,6 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
writeText(ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, JS_MODULE_KIND, moduleName)) writeText(ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, JS_MODULE_KIND, moduleName))
} }
private fun writeJsFile(name: String, text: String): String {
val moduleFileName = "./$name.js"
val file = File(jsDir, moduleFileName)
if (file.exists()) {
file.delete()
}
file.writeAsJsModule(text, moduleFileName)
return file.canonicalPath
}
private fun prepareExternalJsFiles(): MutableList<String> { private fun prepareExternalJsFiles(): MutableList<String> {
jsDir.invalidateDir() jsDir.invalidateDir()
return testDir.filesInDir.mapNotNullTo(mutableListOf(MODULE_EMULATION_FILE)) { file -> return testDir.filesInDir.mapNotNullTo(mutableListOf(MODULE_EMULATION_FILE)) { file ->
@@ -249,12 +240,15 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
private fun verifyJsCode(stepId: Int, mainModuleName: String, jsOutput: CompilationOutputs) { private fun verifyJsCode(stepId: Int, mainModuleName: String, jsOutput: CompilationOutputs) {
val files = prepareExternalJsFiles() val files = prepareExternalJsFiles()
jsOutput.dependencies.mapTo(files) { writeJsFile(it.first, it.second.jsCode) } val compiledJsFiles = jsOutput.writeAll(jsDir, mainModuleName, true, mainModuleName, JS_MODULE_KIND)
files += writeJsFile(mainModuleName, jsOutput.jsCode) for (jsCodeFile in compiledJsFiles) {
val jsFile = File(jsCodeFile)
jsFile.writeAsJsModule(jsFile.readText(), "./${jsFile.name}")
}
try { try {
V8IrJsTestChecker.checkWithTestFunctionArgs( V8IrJsTestChecker.checkWithTestFunctionArgs(
files = files, files = files + compiledJsFiles,
testModuleName = "./$mainModuleName.js", testModuleName = "./$mainModuleName.js",
testPackageName = null, testPackageName = null,
testFunctionName = BOX_FUNCTION_NAME, testFunctionName = BOX_FUNCTION_NAME,
@@ -269,11 +263,16 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
} }
} }
private fun verifyDTS(stepId: Int, testInfo: List<TestStepInfo>, jsOutput: CompilationOutputs) { private fun verifyDTS(stepId: Int, testInfo: List<TestStepInfo>) {
for (info in testInfo) { for (info in testInfo) {
val expectedDTS = info.expectedDTS ?: continue val expectedDTS = info.expectedDTS ?: continue
val output = jsOutput.dependencies.find { it.first == info.moduleName }?.second ?: jsOutput
val gotDTS = output.getFullTsDefinition(info.moduleName, JS_MODULE_KIND) val dtsFile = jsDir.resolve("${File(info.modulePath).nameWithoutExtension}.d.ts")
JUnit4Assertions.assertTrue(dtsFile.exists()) {
"Cannot find d.ts (${dtsFile.absolutePath}) file for module ${info.moduleName} at step $stepId"
}
val gotDTS = dtsFile.readText()
JUnit4Assertions.assertEquals(expectedDTS, gotDTS) { JUnit4Assertions.assertEquals(expectedDTS, gotDTS) {
"Mismatched d.ts for module ${info.moduleName} at step $stepId" "Mismatched d.ts for module ${info.moduleName} at step $stepId"
} }
@@ -320,7 +319,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true) val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true)
verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS) verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS)
verifyJsCode(projStep.id, mainModuleName, jsOutput) verifyJsCode(projStep.id, mainModuleName, jsOutput)
verifyDTS(projStep.id, testInfo, jsOutput) verifyDTS(projStep.id, testInfo)
} }
} }
} }
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
@@ -36,6 +37,7 @@ import org.jetbrains.kotlin.test.frontend.classic.moduleDescriptorProvider
import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
import java.io.File import java.io.File
@@ -256,17 +258,28 @@ class JsIrBackendFacade(
return this return this
} }
fun File.fixJsFile(newJsTarget: File, moduleId: String, moduleKind: ModuleKind) {
val newJsCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(readText(), moduleKind, moduleId)
val oldJsMap = File("$absolutePath.map")
val jsCodeMap = (moduleKind == ModuleKind.PLAIN && oldJsMap.exists()).ifTrue { oldJsMap.readText() }
this.delete()
oldJsMap.delete()
newJsTarget.write(newJsCode)
jsCodeMap?.let { File("${newJsTarget.absolutePath}.map").write(it) }
}
private fun CompilationOutputs.writeTo(outputFile: File, moduleId: String, moduleKind: ModuleKind) { private fun CompilationOutputs.writeTo(outputFile: File, moduleId: String, moduleKind: ModuleKind) {
val wrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, moduleId = moduleId, moduleKind = moduleKind) val allJsFiles = writeAll(outputFile.parentFile, outputFile.nameWithoutExtension, false, moduleId, moduleKind)
outputFile.write(wrappedCode)
if (moduleKind == ModuleKind.PLAIN) { val mainModuleFile = File(allJsFiles.last())
writeSourceMapIfPresent(outputFile) mainModuleFile.fixJsFile(outputFile, moduleId, moduleKind)
}
dependencies.forEach { (moduleId, outputs) -> dependencies.map { it.first }.zip(allJsFiles.dropLast(1)).forEach { (depModuleId, builtJsFilePath) ->
val moduleWrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(outputs.jsCode, moduleKind, moduleId) val newFile = outputFile.augmentWithModuleName(depModuleId)
outputFile.augmentWithModuleName(moduleId).write(moduleWrappedCode) File(builtJsFilePath).fixJsFile(newFile, depModuleId, moduleKind)
} }
} }
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.js.test.handlers package org.jetbrains.kotlin.js.test.handlers
import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
import org.jetbrains.kotlin.js.backend.ast.JsProgram import org.jetbrains.kotlin.js.backend.ast.JsProgram
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.CacheUpdater import org.jetbrains.kotlin.ir.backend.js.ic.CacheUpdater
import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -168,19 +169,10 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
buildBinaryNoIC(configuration, mainModuleKlibFile, allDependencies) buildBinaryNoIC(configuration, mainModuleKlibFile, allDependencies)
val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() } val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() }
val binaries = ArrayList<File>(allDependencies.regularDependencies.size) val binaries = compilationOutputs.writeAll(binariesDir, MAIN_MODULE_NAME, false, MAIN_MODULE_NAME, ModuleKind.PLAIN).map {
File(it)
for ((name, code) in compilationOutputs.dependencies) {
val depBinary = binariesDir.binJsFile(name)
depBinary.parentFile?.let { if (!it.exists()) it.mkdirs() }
depBinary.writeText(code.jsCode)
binaries.add(depBinary)
} }
val mainBinary = binariesDir.binJsFile(MAIN_MODULE_NAME)
mainBinary.writeText(compilationOutputs.jsCode)
binaries.add(mainBinary)
executeAndCheckBinaries(MAIN_MODULE_NAME, binaries) executeAndCheckBinaries(MAIN_MODULE_NAME, binaries)
} }