From 053bc08626b2439a28dac8c0ee4192f40c14d768 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Thu, 21 Dec 2023 14:19:09 +0000 Subject: [PATCH] [K/Wasm] Add Binaryen sizes to Wasm size tests --- compiler/testData/codegen/box/size/add.kt | 2 + .../testData/codegen/box/size/helloWorld.kt | 2 + .../codegen/box/size/helloWorldDOM.kt | 2 + .../codegen/box/size/objectsOptimization.kt | 2 + compiler/testData/codegen/box/size/ok.kt | 2 + .../codegen/box/size/removeUnusedOverride.kt | 2 + .../WasmEnvironmentConfigurationDirectives.kt | 4 ++ .../kotlin/test/model/ResultingArtifacts.kt | 1 + .../targets/js/binaryen/BinaryenExec.kt | 35 +------------ .../src/main/kotlin/setupBinaryen.kt | 30 +++++++++++ .../kotlin/platform/wasm/BinaryenConfig.kt | 42 +++++++++++++++ wasm/wasm.tests/build.gradle.kts | 2 + .../wasm/test/converters/WasmBackendFacade.kt | 20 +++++++- .../wasm/test/handlers/WasiBoxRunner.kt | 34 +++++++------ .../wasm/test/handlers/WasmBoxRunner.kt | 39 ++++++++++---- .../kotlin/wasm/test/tools/BinaryenRunner.kt | 51 +++++++++++++++++++ .../kotlin/wasm/test/tools/WasmVM.kt | 2 +- 17 files changed, 211 insertions(+), 61 deletions(-) create mode 100644 repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupBinaryen.kt create mode 100644 wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/BinaryenConfig.kt create mode 100644 wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/BinaryenRunner.kt diff --git a/compiler/testData/codegen/box/size/add.kt b/compiler/testData/codegen/box/size/add.kt index eaaf034618d..819e65b33e9 100644 --- a/compiler/testData/codegen/box/size/add.kt +++ b/compiler/testData/codegen/box/size/add.kt @@ -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 diff --git a/compiler/testData/codegen/box/size/helloWorld.kt b/compiler/testData/codegen/box/size/helloWorld.kt index 09bf108f41a..451f294e257 100644 --- a/compiler/testData/codegen/box/size/helloWorld.kt +++ b/compiler/testData/codegen/box/size/helloWorld.kt @@ -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!") diff --git a/compiler/testData/codegen/box/size/helloWorldDOM.kt b/compiler/testData/codegen/box/size/helloWorldDOM.kt index d3b45ed9e66..24f7797da5e 100644 --- a/compiler/testData/codegen/box/size/helloWorldDOM.kt +++ b/compiler/testData/codegen/box/size/helloWorldDOM.kt @@ -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 diff --git a/compiler/testData/codegen/box/size/objectsOptimization.kt b/compiler/testData/codegen/box/size/objectsOptimization.kt index 0fc59bf4d59..2f6797141f1 100644 --- a/compiler/testData/codegen/box/size/objectsOptimization.kt +++ b/compiler/testData/codegen/box/size/objectsOptimization.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 diff --git a/compiler/testData/codegen/box/size/ok.kt b/compiler/testData/codegen/box/size/ok.kt index a75dc1b9df3..214d21dd3a6 100644 --- a/compiler/testData/codegen/box/size/ok.kt +++ b/compiler/testData/codegen/box/size/ok.kt @@ -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" \ No newline at end of file diff --git a/compiler/testData/codegen/box/size/removeUnusedOverride.kt b/compiler/testData/codegen/box/size/removeUnusedOverride.kt index ba9452379f9..797e8a81870 100644 --- a/compiler/testData/codegen/box/size/removeUnusedOverride.kt +++ b/compiler/testData/codegen/box/size/removeUnusedOverride.kt @@ -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" diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/WasmEnvironmentConfigurationDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/WasmEnvironmentConfigurationDirectives.kt index b5962e2a3cc..fd3ec4df2f9 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/WasmEnvironmentConfigurationDirectives.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/WasmEnvironmentConfigurationDirectives.kt @@ -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", + ) } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt index ae20d845adc..f71e5aa2bf8 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt @@ -54,6 +54,7 @@ object BinaryArtifacts { class Wasm( val compilerResult: WasmCompilerResult, val compilerResultWithDCE: WasmCompilerResult, + val compilerResultWithOptimizer: WasmCompilerResult?, ) : ResultingArtifact.Binary() { override val kind: BinaryKind get() = ArtifactKinds.Wasm diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/binaryen/BinaryenExec.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/binaryen/BinaryenExec.kt index 37b8e6e4e37..a5a6555dbf5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/binaryen/BinaryenExec.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/binaryen/BinaryenExec.kt @@ -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::class.java) { } @Input - var binaryenArgs: MutableList = 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 = BinaryenConfig.binaryenArgs.toMutableList() @PathSensitive(PathSensitivity.RELATIVE) @InputFile diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupBinaryen.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupBinaryen.kt new file mode 100644 index 00000000000..b6d8fa3b9c8 --- /dev/null +++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupBinaryen.kt @@ -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()) + } +} diff --git a/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/BinaryenConfig.kt b/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/BinaryenConfig.kt new file mode 100644 index 00000000000..18df5d993d1 --- /dev/null +++ b/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/BinaryenConfig.kt @@ -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", + ) +} \ No newline at end of file diff --git a/wasm/wasm.tests/build.gradle.kts b/wasm/wasm.tests/build.gradle.kts index 4b953ef1a7a..d5f03413747 100644 --- a/wasm/wasm.tests/build.gradle.kts +++ b/wasm/wasm.tests/build.gradle.kts @@ -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") diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt index 4424be8ca65..40ded71724d 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt @@ -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() { + 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? { diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt index 10e7b63963f..b5c53f450ed 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt @@ -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 = 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 = 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) } } } \ No newline at end of file diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt index 45e96de0840..fd62054f40e 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt @@ -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 = 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> +) { 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")) -} +} \ No newline at end of file diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/BinaryenRunner.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/BinaryenRunner.kt new file mode 100644 index 00000000000..2910417fa6d --- /dev/null +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/BinaryenRunner.kt @@ -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, 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() + } + } +} \ No newline at end of file diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt index cf3168ab9e5..88c92083c54 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt @@ -119,5 +119,5 @@ internal class ExternalTool(val path: String) { } } -private fun escapeShellArgument(arg: String): String = +internal fun escapeShellArgument(arg: String): String = "'${arg.replace("'", "'\\''")}'"