[K/Wasm] Add Binaryen sizes to Wasm size tests

This commit is contained in:
Artem Kobzar
2023-12-21 14:19:09 +00:00
committed by Space Team
parent 19fe605a3e
commit 053bc08626
17 changed files with 211 additions and 61 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.wasm.test.converters
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
import org.jetbrains.kotlin.backend.wasm.compileWasm
import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations
@@ -32,12 +33,15 @@ import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigurator
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.wasm.test.handlers.getWasmTestOutputDirectory
import org.jetbrains.kotlin.wasm.test.tools.WasmOptimizer
import java.io.File
class WasmBackendFacade(
private val testServices: TestServices
) : AbstractTestFacade<BinaryArtifacts.KLib, BinaryArtifacts.Wasm>() {
private val supportedOptimizer: WasmOptimizer = WasmOptimizer.Binaryen
override val inputKind = ArtifactKinds.KLib
override val outputKind = ArtifactKinds.Wasm
@@ -126,13 +130,27 @@ class WasmBackendFacade(
return BinaryArtifacts.Wasm(
compilerResult,
compilerResultWithDCE
compilerResultWithDCE,
runIf(WasmEnvironmentConfigurationDirectives.RUN_THIRD_PARTY_OPTIMIZER in testServices.moduleStructure.allDirectives) {
compilerResultWithDCE.runThirdPartyOptimizer()
}
)
}
override fun shouldRunAnalysis(module: TestModule): Boolean {
return WasmEnvironmentConfigurator.isMainModule(module, testServices)
}
private fun WasmCompilerResult.runThirdPartyOptimizer(): WasmCompilerResult {
val (newWasm, newWat) = supportedOptimizer.run(wasm, withText = wat != null)
return WasmCompilerResult(
wat = newWat,
jsUninstantiatedWrapper = jsUninstantiatedWrapper,
jsWrapper = jsWrapper,
sourceMap = null,
wasm = newWasm
)
}
}
fun extractTestPackage(testServices: TestServices): String? {
@@ -7,12 +7,9 @@ package org.jetbrains.kotlin.wasm.test.handlers
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.writeCompilationResult
import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.test.DebugMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.RUN_UNIT_TESTS
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.moduleStructure
import org.jetbrains.kotlin.wasm.test.tools.WasmVM
@@ -21,6 +18,8 @@ import java.io.File
class WasiBoxRunner(
testServices: TestServices
) : AbstractWasmArtifactsCollector(testServices) {
private val vmsToCheck: List<WasmVM> = listOf(WasmVM.NodeJs)
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (!someAssertionWasFailed) {
runWasmCode()
@@ -66,6 +65,7 @@ class WasiBoxRunner(
dir.mkdirs()
writeCompilationResult(res, dir, baseFileName)
File(dir, "test.mjs").writeText(testWasi)
if (debugMode >= DebugMode.DEBUG) {
@@ -79,25 +79,27 @@ class WasiBoxRunner(
val testFileText = originalFile.readText()
val failsIn: List<String> = InTextDirectivesUtils.findListWithPrefixes(testFileText, "// WASM_FAILS_IN: ")
val exception = WasmVM.NodeJs.runWithCathedExceptions(
debugMode = debugMode,
disableExceptions = false,
failsIn = failsIn,
entryMjs = "test.mjs",
jsFilePaths = emptyList(),
workingDirectory = dir
)
if (exception != null) {
throw exception
val exceptions = vmsToCheck.mapNotNull { vm ->
vm.runWithCathedExceptions(
debugMode = debugMode,
disableExceptions = false,
failsIn = failsIn,
entryMjs = "test.mjs",
jsFilePaths = emptyList(),
workingDirectory = dir
)
}
if (mode == "dce") {
checkExpectedOutputSize(debugMode, testFileText, dir)
processExceptions(exceptions)
when (mode) {
"dce" -> checkExpectedDceOutputSize(debugMode, testFileText, dir)
"optimized" -> checkExpectedOptimizedOutputSize(debugMode, testFileText, dir)
}
}
writeToFilesAndRunTest("dev", artifacts.compilerResult)
writeToFilesAndRunTest("dce", artifacts.compilerResultWithDCE)
artifacts.compilerResultWithOptimizer?.let { writeToFilesAndRunTest("optimized", it) }
}
}
@@ -7,13 +7,10 @@ package org.jetbrains.kotlin.wasm.test.handlers
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.writeCompilationResult
import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.test.DebugMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.DISABLE_WASM_EXCEPTION_HANDLING
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.RUN_UNIT_TESTS
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.moduleStructure
import org.jetbrains.kotlin.wasm.test.tools.WasmVM
@@ -22,6 +19,8 @@ import java.io.File
class WasmBoxRunner(
testServices: TestServices
) : AbstractWasmArtifactsCollector(testServices) {
private val vmsToCheck: List<WasmVM> = listOf(WasmVM.V8, WasmVM.SpiderMonkey)
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (!someAssertionWasFailed) {
runWasmCode()
@@ -71,6 +70,7 @@ class WasmBoxRunner(
dir.mkdirs()
writeCompilationResult(res, dir, baseFileName)
File(dir, "test.mjs").writeText(testJs)
val (jsFilePaths) = collectedJsArtifacts.saveJsArtifacts(dir)
@@ -117,7 +117,7 @@ class WasmBoxRunner(
val disableExceptions = DISABLE_WASM_EXCEPTION_HANDLING in testServices.moduleStructure.allDirectives
val exceptions = listOf(WasmVM.V8, WasmVM.SpiderMonkey).mapNotNull { vm ->
val exceptions = vmsToCheck.mapNotNull { vm ->
vm.runWithCathedExceptions(
debugMode = debugMode,
disableExceptions = disableExceptions,
@@ -130,13 +130,15 @@ class WasmBoxRunner(
processExceptions(exceptions)
if (mode == "dce") {
checkExpectedOutputSize(debugMode, testFileText, dir)
when (mode) {
"dce" -> checkExpectedDceOutputSize(debugMode, testFileText, dir)
"optimized" -> checkExpectedOptimizedOutputSize(debugMode, testFileText, dir)
}
}
writeToFilesAndRunTest("dev", artifacts.compilerResult)
writeToFilesAndRunTest("dce", artifacts.compilerResultWithDCE)
artifacts.compilerResultWithOptimizer?.let { writeToFilesAndRunTest("optimized", it) }
}
}
@@ -169,8 +171,8 @@ internal fun WasmVM.runWithCathedExceptions(
return null
}
fun checkExpectedOutputSize(debugMode: DebugMode, testFileContent: String, testDir: File) {
val expectedSizes =
fun checkExpectedDceOutputSize(debugMode: DebugMode, testFileContent: String, testDir: File) {
val expectedDceSizes =
InTextDirectivesUtils.findListWithPrefixes(testFileContent, "// WASM_DCE_EXPECTED_OUTPUT_SIZE: ")
.map {
val i = it.indexOf(' ')
@@ -178,10 +180,27 @@ fun checkExpectedOutputSize(debugMode: DebugMode, testFileContent: String, testD
val size = it.substring(i + 1)
extension.trim().lowercase() to size.filter(Char::isDigit).toInt()
}
assertExpectedSizesMatchActual(debugMode, testDir, expectedDceSizes)
}
fun checkExpectedOptimizedOutputSize(debugMode: DebugMode, testFileContent: String, testDir: File) {
val expectedOptimizeSizes = InTextDirectivesUtils
.findListWithPrefixes(testFileContent, "// WASM_OPT_EXPECTED_OUTPUT_SIZE: ")
.lastOrNull()
?.filter(Char::isDigit)
?.toInt() ?: return
assertExpectedSizesMatchActual(debugMode, testDir, listOf("wasm" to expectedOptimizeSizes))
}
private fun assertExpectedSizesMatchActual(
debugMode: DebugMode,
testDir: File,
fileExtensionToItsExpectedSize: Iterable<Pair<String, Int>>
) {
val filesByExtension = testDir.listFiles()?.groupBy { it.extension }.orEmpty()
val errors = expectedSizes.mapNotNull { (extension, expectedSize) ->
val errors = fileExtensionToItsExpectedSize.mapNotNull { (extension, expectedSize) ->
val totalSize = filesByExtension[extension].orEmpty().sumOf { it.length() }
val thresholdPercent = 1
@@ -204,4 +223,4 @@ fun checkExpectedOutputSize(debugMode: DebugMode, testFileContent: String, testD
}
if (errors.isNotEmpty()) throw AssertionError(errors.joinToString("\n"))
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2023 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.wasm.test.tools
import org.jetbrains.kotlin.platform.wasm.BinaryenConfig
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.io.ByteArrayOutputStream
import java.io.File
import kotlin.test.fail
sealed interface WasmOptimizer {
fun run(wasmInput: ByteArray, withText: Boolean = false): OptimizationResult
data class OptimizationResult(val wasm: ByteArray, val wat: String?)
object Binaryen : WasmOptimizer {
private val binaryenPath = System.getProperty("binaryen.path")
override fun run(wasmInput: ByteArray, withText: Boolean): OptimizationResult {
val command = arrayOf(binaryenPath, *BinaryenConfig.binaryenArgs.toTypedArray())
return OptimizationResult(
exec(command, wasmInput),
runIf(withText) { exec(command + "-S", wasmInput).toString(Charsets.UTF_8) }
)
}
private fun exec(command: Array<String>, input: ByteArray): ByteArray {
val timestamp = System.currentTimeMillis()
val savedInput = File
.createTempFile("binaryen_input_$timestamp", ".wasm")
.apply { writeBytes(input) }
val savedOutput = File
.createTempFile("binaryen_output_$timestamp", ".wasm")
val processBuilder = ProcessBuilder(*(command + listOf("-o", savedOutput.absolutePath, savedInput.absolutePath)))
.redirectErrorStream(true)
val exitValue = processBuilder.start().waitFor()
if (exitValue != 0) {
val commandString = command.joinToString(" ") { escapeShellArgument(it) }
fail("Command \"$commandString\" terminated with exit code $exitValue")
}
return savedOutput.readBytes()
}
}
}
@@ -119,5 +119,5 @@ internal class ExternalTool(val path: String) {
}
}
private fun escapeShellArgument(arg: String): String =
internal fun escapeShellArgument(arg: String): String =
"'${arg.replace("'", "'\\''")}'"