[K/Wasm] Add Binaryen sizes to Wasm size tests
This commit is contained in:
+19
-1
@@ -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("'", "'\\''")}'"
|
||||
|
||||
Reference in New Issue
Block a user