[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
+2
View File
@@ -1,7 +1,9 @@
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_559
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 4_526
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 2_640
// FILE: test.kt
+2
View File
@@ -1,7 +1,9 @@
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 38_992
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 4_552
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 9_847
fun box(): String {
println("Hello, World!")
+2
View File
@@ -1,7 +1,9 @@
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_922
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_081
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 4_317
// FILE: test.kt
@@ -1,7 +1,9 @@
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 17_619
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 4_398
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 5_841
object Simple
+2
View File
@@ -1,6 +1,8 @@
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_601
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 4_398
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 3_743
fun box() = "OK"
@@ -1,7 +1,9 @@
// TARGET_BACKEND: JS_IR
// TARGET_BACKEND: WASM
// RUN_THIRD_PARTY_OPTIMIZER
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_109
// WASM_OPT_EXPECTED_OUTPUT_SIZE: 3_900
interface I {
fun foo() = "OK"
@@ -39,4 +39,8 @@ object WasmEnvironmentConfigurationDirectives : SimpleDirectivesContainer() {
description = "Enables generation of source map",
applicability = DirectiveApplicability.Global
)
val RUN_THIRD_PARTY_OPTIMIZER by directive(
description = "Also run third-party optimizer (for now, only binaryen is supported) after the main compilation",
)
}
@@ -54,6 +54,7 @@ object BinaryArtifacts {
class Wasm(
val compilerResult: WasmCompilerResult,
val compilerResultWithDCE: WasmCompilerResult,
val compilerResultWithOptimizer: WasmCompilerResult?,
) : ResultingArtifact.Binary<Wasm>() {
override val kind: BinaryKind<Wasm>
get() = ArtifactKinds.Wasm
@@ -13,6 +13,7 @@ import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.newFileProperty
import org.jetbrains.kotlin.platform.wasm.BinaryenConfig
import javax.inject.Inject
@DisableCachingByDefault
@@ -33,39 +34,7 @@ constructor() : AbstractExecTask<BinaryenExec>(BinaryenExec::class.java) {
}
@Input
var binaryenArgs: MutableList<String> = mutableListOf(
// Proposals
"--enable-gc",
"--enable-reference-types",
"--enable-exception-handling",
"--enable-bulk-memory", // For array initialization from data sections
// Other options
"--enable-nontrapping-float-to-int",
// It's turned out that it's not safe
// "--closed-world",
// Optimizations:
// Note the order and repetition of the next options matter.
//
// About Binaryen optimizations:
// GC Optimization Guidebook -- https://github.com/WebAssembly/binaryen/wiki/GC-Optimization-Guidebook
// Optimizer Cookbook -- https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook
//
"--inline-functions-with-loops",
"--traps-never-happen",
"--fast-math",
// without "--type-merging" it produces increases the size
// "--type-ssa",
"-O3",
"-O3",
"--gufa",
"-O3",
// requires --closed-world
// "--type-merging",
"-O3",
"-Oz",
)
var binaryenArgs: MutableList<String> = BinaryenConfig.binaryenArgs.toMutableList()
@PathSensitive(PathSensitivity.RELATIVE)
@InputFile
@@ -0,0 +1,30 @@
/*
* 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.
*/
import org.gradle.api.Project
import org.gradle.api.tasks.testing.Test
import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenRootExtension
import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenRootPlugin
private object BinaryenUtils {
lateinit var binaryenPlugin: BinaryenRootExtension
fun useBinaryenPlugin(project: Project) {
binaryenPlugin = BinaryenRootPlugin.apply(project.rootProject)
}
}
fun Project.useBinaryenPlugin() {
BinaryenUtils.useBinaryenPlugin(this)
}
fun Test.setupBinaryen() {
dependsOn(BinaryenUtils.binaryenPlugin.setupTaskProvider)
val binaryenExecutablePath = project.provider {
BinaryenUtils.binaryenPlugin.requireConfigured().executablePath.absolutePath
}
doFirst {
systemProperty("binaryen.path", binaryenExecutablePath.get())
}
}
@@ -0,0 +1,42 @@
/*
* 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.platform.wasm
object BinaryenConfig {
val binaryenArgs = listOf(
// Proposals
"--enable-gc",
"--enable-reference-types",
"--enable-exception-handling",
"--enable-bulk-memory", // For array initialization from data sections
// Other options
"--enable-nontrapping-float-to-int",
// It's turned out that it's not safe
// "--closed-world",
// Optimizations:
// Note the order and repetition of the next options matter.
//
// About Binaryen optimizations:
// GC Optimization Guidebook -- https://github.com/WebAssembly/binaryen/wiki/GC-Optimization-Guidebook
// Optimizer Cookbook -- https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook
//
"--inline-functions-with-loops",
"--traps-never-happen",
"--fast-math",
// without "--type-merging" it produces increases the size
// "--type-ssa",
"-O3",
"-O3",
"--gufa",
"-O3",
// requires --closed-world
// "--type-merging",
"-O3",
"-Oz",
)
}
+2
View File
@@ -79,6 +79,7 @@ val generationRoot = projectDir.resolve("tests-gen")
useD8Plugin()
useNodeJsPlugin()
useBinaryenPlugin()
optInToExperimentalCompilerApi()
sourceSets {
@@ -147,6 +148,7 @@ fun Project.wasmProjectTest(
workingDir = rootDir
setupV8()
setupNodeJs()
setupBinaryen()
setupSpiderMonkey()
useJUnitPlatform()
setupWasmStdlib("js")
@@ -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,7 +79,8 @@ class WasiBoxRunner(
val testFileText = originalFile.readText()
val failsIn: List<String> = InTextDirectivesUtils.findListWithPrefixes(testFileText, "// WASM_FAILS_IN: ")
val exception = WasmVM.NodeJs.runWithCathedExceptions(
val exceptions = vmsToCheck.mapNotNull { vm ->
vm.runWithCathedExceptions(
debugMode = debugMode,
disableExceptions = false,
failsIn = failsIn,
@@ -87,17 +88,18 @@ class WasiBoxRunner(
jsFilePaths = emptyList(),
workingDirectory = dir
)
if (exception != null) {
throw exception
}
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
@@ -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("'", "'\\''")}'"