[WASM] Run box tests with dce and non-dce modes

This commit is contained in:
Igor Yakovlev
2022-01-31 20:01:22 +01:00
committed by TeamCityServer
parent 9a967952b5
commit 1446914ba9
3 changed files with 134 additions and 83 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.common.CompilationException
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.wasm.compileWasm import org.jetbrains.kotlin.backend.wasm.compileWasm
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
import org.jetbrains.kotlin.backend.wasm.wasmPhases import org.jetbrains.kotlin.backend.wasm.wasmPhases
import org.jetbrains.kotlin.cli.common.* import org.jetbrains.kotlin.cli.common.*
import org.jetbrains.kotlin.cli.common.ExitCode.* import org.jetbrains.kotlin.cli.common.ExitCode.*
@@ -31,7 +32,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
@@ -321,13 +321,17 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
if (arguments.wasm) { if (arguments.wasm) {
val res = compileWasm( val (moduleFragment, backendContext) = compileToLoweredIr(
module, depsDescriptors = module,
PhaseConfig(wasmPhases), phaseConfig = PhaseConfig(wasmPhases),
IrFactoryImpl, irFactory = IrFactoryImpl,
exportedDeclarations = setOf(FqName("main")), exportedDeclarations = setOf(FqName("main")),
emitNameSection = arguments.wasmDebug,
propertyLazyInitialization = arguments.irPropertyLazyInitialization, propertyLazyInitialization = arguments.irPropertyLazyInitialization,
)
val res = compileWasm(
moduleFragment = moduleFragment,
backendContext = backendContext,
emitNameSection = arguments.wasmDebug,
dceEnabled = arguments.irDce, dceEnabled = arguments.irDce,
) )
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!! val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.ir.backend.js.loadIr import org.jetbrains.kotlin.ir.backend.js.loadIr
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.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.noUnboundLeft import org.jetbrains.kotlin.ir.util.noUnboundLeft
import org.jetbrains.kotlin.ir.util.patchDeclarationParents import org.jetbrains.kotlin.ir.util.patchDeclarationParents
@@ -25,15 +26,13 @@ import java.io.ByteArrayOutputStream
class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray) class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray)
fun compileWasm( fun compileToLoweredIr(
depsDescriptors: ModulesStructure, depsDescriptors: ModulesStructure,
phaseConfig: PhaseConfig, phaseConfig: PhaseConfig,
irFactory: IrFactory, irFactory: IrFactory,
exportedDeclarations: Set<FqName> = emptySet(), exportedDeclarations: Set<FqName> = emptySet(),
propertyLazyInitialization: Boolean, propertyLazyInitialization: Boolean,
emitNameSection: Boolean = false, ): Pair<IrModuleFragment, WasmBackendContext> {
dceEnabled: Boolean = false,
): WasmCompilerResult {
val mainModule = depsDescriptors.mainModule val mainModule = depsDescriptors.mainModule
val configuration = depsDescriptors.compilerConfiguration val configuration = depsDescriptors.compilerConfiguration
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr( val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(
@@ -71,12 +70,22 @@ fun compileWasm(
wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment) wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment)
return Pair(moduleFragment, context)
}
fun compileWasm(
moduleFragment: IrModuleFragment,
backendContext: WasmBackendContext,
emitNameSection: Boolean = false,
dceEnabled: Boolean = false,
): WasmCompilerResult {
if (dceEnabled) { if (dceEnabled) {
eliminateDeadDeclarations(listOf(moduleFragment), context) eliminateDeadDeclarations(listOf(moduleFragment), backendContext)
} }
val compiledWasmModule = WasmCompiledModuleFragment(context.irBuiltIns) val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
val codeGenerator = WasmModuleFragmentGenerator(context, compiledWasmModule, allowIncompleteImplementations = dceEnabled) val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = dceEnabled)
codeGenerator.generateModule(moduleFragment) codeGenerator.generateModule(moduleFragment)
val linkedModule = compiledWasmModule.linkWasmCompiledFragments() val linkedModule = compiledWasmModule.linkWasmCompiledFragments()
@@ -87,7 +96,7 @@ fun compileWasm(
val js = compiledWasmModule.generateJs() val js = compiledWasmModule.generateJs()
val os = ByteArrayOutputStream() val os = ByteArrayOutputStream()
WasmIrToBinary(os, linkedModule, moduleDescriptor.name.asString(), emitNameSection).appendWasmModule() WasmIrToBinary(os, linkedModule, moduleFragment.descriptor.name.asString(), emitNameSection).appendWasmModule()
val byteArray = os.toByteArray() val byteArray = os.toByteArray()
return WasmCompilerResult( return WasmCompilerResult(
@@ -97,7 +106,6 @@ fun compileWasm(
) )
} }
fun WasmCompiledModuleFragment.generateJs(): String { fun WasmCompiledModuleFragment.generateJs(): String {
//language=js //language=js
val runtime = """ val runtime = """
@@ -13,6 +13,7 @@ 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.backend.wasm.WasmCompilerResult import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.compileWasm import org.jetbrains.kotlin.backend.wasm.compileWasm
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
import org.jetbrains.kotlin.backend.wasm.wasmPhases import org.jetbrains.kotlin.backend.wasm.wasmPhases
import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
@@ -20,6 +21,7 @@ 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.*
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -64,10 +66,15 @@ abstract class BasicWasmBoxTest(
val inputFiles: MutableList<TestFile> = TestFiles.createTestFiles(file.name, fileContent, testFactory, true) val inputFiles: MutableList<TestFile> = TestFiles.createTestFiles(file.name, fileContent, testFactory, true)
val testPackage = testFactory.testPackage val testPackage = testFactory.testPackage
val outputFileBase = outputDir.absolutePath + "/" + getTestName(true) val outputFileBase = outputDir.absolutePath + "/" + getTestName(true)
val outputWatFile = outputFileBase + ".wat" val outputWatFile = File("$outputFileBase.wat")
val outputWasmFile = outputFileBase + ".wasm" val outputWasmFile = File("$outputFileBase.wasm")
val outputJsFile = outputFileBase + ".js" val outputJsFile = File("$outputFileBase.js")
val outputBrowserDir = outputFileBase + ".browser" val outputBrowserDir = File("$outputFileBase.browser")
val outputFileNoDceBase = outputDir.absolutePath + "/" + getTestName(true) + "/noDce"
val outputWatNoDceFile = File("$outputFileNoDceBase.wat")
val outputWasmNoDceFile = File("$outputFileNoDceBase.wasm")
val outputJsNoDceFile = File("$outputFileNoDceBase.js")
val outputBrowserNoDceDir = File("$outputFileNoDceBase.browser")
val languageVersionSettings = inputFiles.mapNotNull { it.languageVersionSettings }.firstOrNull() val languageVersionSettings = inputFiles.mapNotNull { it.languageVersionSettings }.firstOrNull()
val kotlinFiles = mutableListOf<String>() val kotlinFiles = mutableListOf<String>()
@@ -100,27 +107,72 @@ abstract class BasicWasmBoxTest(
val psiFiles = createPsiFiles(allSourceFiles.map { File(it).canonicalPath }.sorted()) val psiFiles = createPsiFiles(allSourceFiles.map { File(it).canonicalPath }.sorted())
val config = createConfig(languageVersionSettings) val config = createConfig(languageVersionSettings)
translateFiles( val filesToCompile = psiFiles.map { TranslationUnit.SourceFile(it).file }
file, val debugMode = DebugMode.fromSystemProperty("kotlin.wasm.debugMode")
psiFiles.map(TranslationUnit::SourceFile),
File(outputWatFile), val phaseConfig = if (debugMode >= DebugMode.DEBUG) {
File(outputWasmFile), val allPhasesSet = if (debugMode >= DebugMode.SUPER_DEBUG) wasmPhases.toPhaseMap().values.toSet() else emptySet()
File(outputJsFile), val dumpOutputDir = File(outputWatFile.parent, outputWatFile.nameWithoutExtension + "-irdump")
File(outputBrowserDir), println("\n ------ Dumping phases to file://$dumpOutputDir")
config, println(" ------ KT file://${file.absolutePath}")
testPackage, println(" ------ WAT file://$outputWatFile")
TEST_FUNCTION println(" ------ WASM file://$outputWasmFile")
println(" ------ JS file://$outputJsFile")
println(" ------ (No-DCE files in noDce sub-directory)")
PhaseConfig(
wasmPhases,
dumpToDirectory = dumpOutputDir.path,
toDumpStateAfter = allPhasesSet,
// toValidateStateAfter = allPhasesSet,
// dumpOnlyFqName = null
)
} else {
PhaseConfig(wasmPhases)
}
val sourceModule = prepareAnalyzedSourceModule(
config.project,
filesToCompile,
config.configuration,
// TODO: Bypass the resolver fow wasm.
listOf(System.getProperty("kotlin.wasm.stdlib.path")!!, System.getProperty("kotlin.wasm.kotlin.test.path")!!),
emptyList(),
AnalyzerWithCompilerReport(config.configuration)
) )
ExternalTool(System.getProperty("javascript.engine.path.V8")) File(outputFileNoDceBase).mkdirs()
.run( compileAndRun(
"--experimental-wasm-typed-funcref", phaseConfig = phaseConfig,
"--experimental-wasm-gc", sourceModule = sourceModule,
"--experimental-wasm-eh", config = config,
*jsFilesBefore.toTypedArray(), testPackage = testPackage,
outputJsFile, testFunction = TEST_FUNCTION,
*jsFilesAfter.toTypedArray(), dceEnabled = false,
) outputWatFile = outputWatNoDceFile,
outputWasmFile = outputWasmNoDceFile,
outputJsFile = outputJsNoDceFile,
outputBrowserDir = outputBrowserNoDceDir,
debugMode = debugMode,
jsFilesBefore = jsFilesBefore,
jsFilesAfter = jsFilesAfter
)
File(outputFileBase).mkdirs()
compileAndRun(
phaseConfig = phaseConfig,
sourceModule = sourceModule,
config = config,
testPackage = testPackage,
testFunction = TEST_FUNCTION,
dceEnabled = true,
outputBrowserDir = outputBrowserDir,
debugMode = debugMode,
outputWatFile = outputWatFile,
outputWasmFile = outputWasmFile,
outputJsFile = outputJsFile,
jsFilesBefore = jsFilesBefore,
jsFilesAfter = jsFilesAfter
)
} }
} }
@@ -133,56 +185,34 @@ abstract class BasicWasmBoxTest(
.fold(testGroupOutputDir, ::File) .fold(testGroupOutputDir, ::File)
} }
private fun translateFiles( private fun compileAndRun(
testFile: File, phaseConfig: PhaseConfig,
units: List<TranslationUnit>, sourceModule: ModulesStructure,
config: JsConfig,
testPackage: String?,
testFunction: String,
dceEnabled: Boolean,
outputWatFile: File, outputWatFile: File,
outputWasmFile: File, outputWasmFile: File,
outputJsFile: File, outputJsFile: File,
outputBrowserDir: File, outputBrowserDir: File,
config: JsConfig, debugMode: DebugMode,
testPackage: String?, jsFilesBefore: List<String>,
testFunction: String jsFilesAfter: List<String>,
) { ) {
val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } val (moduleFragment, backendContext) = compileToLoweredIr(
val debugMode = DebugMode.fromSystemProperty("kotlin.wasm.debugMode") depsDescriptors = sourceModule,
val phaseConfig = if (debugMode >= DebugMode.DEBUG) {
val allPhasesSet = if (debugMode >= DebugMode.SUPER_DEBUG) wasmPhases.toPhaseMap().values.toSet() else emptySet()
val dumpOutputDir = File(outputWatFile.parent, outputWatFile.nameWithoutExtension + "-irdump")
println("\n ------ Dumping phases to file://$dumpOutputDir")
println("\n ------ KT file://${testFile.absolutePath}")
println("\n ------ WAT file://$outputWatFile")
println("\n ------ WASM file://$outputWasmFile")
println(" ------ JS file://$outputJsFile")
PhaseConfig(
wasmPhases,
dumpToDirectory = dumpOutputDir.path,
toDumpStateAfter = allPhasesSet,
// toValidateStateAfter = allPhasesSet,
// dumpOnlyFqName = null
)
} else {
PhaseConfig(wasmPhases)
}
val sourceModule = prepareAnalyzedSourceModule(
config.project,
filesToCompile,
config.configuration,
// TODO: Bypass the resolver fow wasm.
listOf(System.getProperty("kotlin.wasm.stdlib.path")!!, System.getProperty("kotlin.wasm.kotlin.test.path")!!),
emptyList(),
AnalyzerWithCompilerReport(config.configuration)
)
val compilerResult = compileWasm(
sourceModule,
phaseConfig = phaseConfig, phaseConfig = phaseConfig,
irFactory = IrFactoryImpl, irFactory = IrFactoryImpl,
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
propertyLazyInitialization = true, propertyLazyInitialization = true,
)
val compilerResult = compileWasm(
moduleFragment = moduleFragment,
backendContext = backendContext,
emitNameSection = true, emitNameSection = true,
dceEnabled = true, dceEnabled = dceEnabled,
) )
outputWatFile.write(compilerResult.wat) outputWatFile.write(compilerResult.wat)
@@ -200,17 +230,26 @@ abstract class BasicWasmBoxTest(
const actualResult = wasmInstance.exports.$testFunction(); const actualResult = wasmInstance.exports.$testFunction();
if (actualResult !== "OK") if (actualResult !== "OK")
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`; throw `Wrong box result '${'$'}{actualResult}' (with DCE=${dceEnabled}); Expected "OK"`;
""".trimIndent() """.trimIndent()
outputJsFile.write(compilerResult.js + "\n" + testRunner) outputJsFile.write(compilerResult.js + "\n" + testRunner)
if (debugMode >= DebugMode.SUPER_DEBUG) { if (debugMode >= DebugMode.SUPER_DEBUG) {
createDirectoryToRunInBrowser(outputBrowserDir, compilerResult) createDirectoryToRunInBrowser(outputBrowserDir, compilerResult, dceEnabled)
} }
ExternalTool(System.getProperty("javascript.engine.path.V8"))
.run(
"--experimental-wasm-typed-funcref",
"--experimental-wasm-gc",
"--experimental-wasm-eh",
*jsFilesBefore.toTypedArray(),
outputJsFile.absolutePath,
*jsFilesAfter.toTypedArray(),
)
} }
private fun createDirectoryToRunInBrowser(directory: File, compilerResult: WasmCompilerResult) { private fun createDirectoryToRunInBrowser(directory: File, compilerResult: WasmCompilerResult, dceEnabled: Boolean) {
val browserRunner = val browserRunner =
""" """
const response = await fetch("index.wasm"); const response = await fetch("index.wasm");
@@ -221,7 +260,7 @@ abstract class BasicWasmBoxTest(
const actualResult = wasmInstance.exports.box(); const actualResult = wasmInstance.exports.box();
if (actualResult !== "OK") if (actualResult !== "OK")
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`; throw `Wrong box result '${'$'}{actualResult}' (with DCE=${dceEnabled}); Expected "OK"`;
console.log("Test passed!"); console.log("Test passed!");
""".trimIndent() """.trimIndent()