From 4636f7107677787f1270f783e2cca2b06ca573e5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Thu, 10 Feb 2022 22:59:44 +0000 Subject: [PATCH] [Wasm] Loader improvements - Output ES modules instead of plain files - Support -Xwasm-launcher=d8 for d8 shell used in tests and benchmarks. - Reuse launcher generation logic in CLI and box tests runners. - Create separate output directory for each box since there are multiple output files generated for each test. - Stop using absolute paths in generate JS files to simplify running generated code on different machine - Remove ">>>" from println output Merge-request: KT-MR-5729 Merged-by: Svyatoslav Kuzmich --- .../common/arguments/K2JSCompilerArguments.kt | 2 +- .../jetbrains/kotlin/cli/js/K2JsIrCompiler.kt | 52 ++-- .../jetbrains/kotlin/backend/wasm/compiler.kt | 80 +++++- .../jetbrains/kotlin/backend/wasm/dce/Dce.kt | 2 +- .../wasm/lower/JsInteropFunctionsLowering.kt | 2 +- compiler/testData/cli/js/jsExtraHelp.out | 2 +- .../codegen/boxWasmJsInterop/functionTypes.kt | 8 + .../codegen/boxWasmJsInterop/jsExport.kt | 10 + .../kotlin/js/testOld/BasicWasmBoxTest.kt | 256 +++++++----------- .../kotlin/js/testOld/engines/SpiderMonkey.kt | 21 +- .../kotlin/wasm/internal/ExternalWrapper.kt | 6 +- libraries/stdlib/wasm/src/kotlin/io.kt | 6 +- 12 files changed, 236 insertions(+), 211 deletions(-) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 0c4f47b2c01..fd1d9e5b9b3 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -242,7 +242,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() { @Argument( value = "-Xwasm-launcher", - valueDescription = "esm|nodejs", + valueDescription = "esm|nodejs|d8", description = "Picks flavor for the wasm launcher. Default is ESM." ) var wasmLauncher: String? by NullableStringFreezableVar("esm") diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 2dfcfe4cd99..00431d2b3ba 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -11,9 +11,11 @@ import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.backend.common.CompilationException import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion +import org.jetbrains.kotlin.backend.wasm.WasmLoaderKind 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.writeCompilationResult import org.jetbrains.kotlin.cli.common.* import org.jetbrains.kotlin.cli.common.ExitCode.* import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments @@ -38,6 +40,7 @@ import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer +import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCaches @@ -326,49 +329,30 @@ class K2JsIrCompiler : CLICompiler() { exportedDeclarations = setOf(FqName("main")), propertyLazyInitialization = arguments.irPropertyLazyInitialization, ) + if (arguments.irDce) { + eliminateDeadDeclarations(allModules, backendContext) + } val res = compileWasm( allModules = allModules, backendContext = backendContext, emitNameSection = arguments.wasmDebug, - dceEnabled = arguments.irDce, + allowIncompleteImplementations = arguments.irDce, ) - val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!! - outputWasmFile.writeBytes(res.wasm) - val outputWatFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wat")!! - outputWatFile.writeText(res.wat) - val esmRunner = """ - export default WebAssembly.instantiateStreaming(fetch('${outputWasmFile.name}'), { runtime, js_code }).then((it) => { - wasmInstance = it.instance; - wasmInstance.exports.__init?.(); - wasmInstance.exports.startUnitTests?.(); - - return it.instance.exports; - }); - """.trimIndent() - - val nodeRunner = """ - const fs = require('fs'); - var path = require('path'); - const wasmBuffer = fs.readFileSync(path.resolve(__dirname, './${outputWasmFile.name}')); - - module.exports = WebAssembly.instantiate(wasmBuffer, { runtime, js_code }).then(wasm => { - wasmInstance = wasm.instance; - - wasmInstance.exports.__init?.(); - wasmInstance.exports.startUnitTests?.(); - - return wasmInstance.exports - }); - """.trimIndent() - - val runner = when (arguments.wasmLauncher) { - "esm" -> esmRunner - "nodejs" -> nodeRunner + val launcherKind = when (arguments.wasmLauncher) { + "esm" -> WasmLoaderKind.BROWSER + "nodejs" -> WasmLoaderKind.NODE + "d8" -> WasmLoaderKind.D8 else -> throw IllegalArgumentException("Unrecognized flavor for the wasm launcher") } - outputFile.writeText(res.js + "\n" + runner) + writeCompilationResult( + result = res, + dir = outputFile.parentFile, + loaderKind = launcherKind, + fileNameBase = outputFile.nameWithoutExtension + ) + return OK } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index ea0eda73051..a9956855a17 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.backend.wasm import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel -import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations @@ -23,6 +22,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText import java.io.ByteArrayOutputStream +import java.io.File class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray) @@ -75,15 +75,10 @@ fun compileWasm( allModules: List, backendContext: WasmBackendContext, emitNameSection: Boolean = false, - dceEnabled: Boolean = false, + allowIncompleteImplementations: Boolean = false, ): WasmCompilerResult { - - if (dceEnabled) { - eliminateDeadDeclarations(allModules, backendContext) - } - val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns) - val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = dceEnabled) + val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations) allModules.forEach { codeGenerator.generateModule(it) } val linkedModule = compiledWasmModule.linkWasmCompiledFragments() @@ -107,13 +102,76 @@ fun compileWasm( fun WasmCompiledModuleFragment.generateJs(): String { //language=js val runtime = """ - var wasmInstance = null; const externrefBoxes = new WeakMap(); """.trimIndent() - val jsFuns = jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode } - val jsCode = "\nconst js_code = {$jsFuns};" + val jsCodeBody = jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode } + val jsCodeBodyIndented = jsCodeBody.prependIndent(" ") + val jsCode = + "\nconst js_code = {\n$jsCodeBodyIndented\n};\n" return runtime + jsCode } + +enum class WasmLoaderKind { + D8, + NODE, + BROWSER, +} + +fun generateJsWasmLoader(kind: WasmLoaderKind, wasmFilePath: String, externalJs: String): String { + val instantiation = when (kind) { + WasmLoaderKind.D8 -> + """ + const wasmModule = new WebAssembly.Module(read('$wasmFilePath', 'binary')); + const wasmInstance = new WebAssembly.Instance(wasmModule, { js_code }); + """.trimIndent() + + WasmLoaderKind.NODE -> + """ + const fs = require('fs'); + var path = require('path'); + const wasmBuffer = fs.readFileSync(path.resolve(__dirname, './$wasmFilePath')); + const wasmModule = new WebAssembly.Module(wasmBuffer); + const wasmInstance = new WebAssembly.Instance(wasmModule, { js_code }); + """.trimIndent() + + WasmLoaderKind.BROWSER -> + """ + const { wasmInstance } = await WebAssembly.instantiateStreaming(fetch("$wasmFilePath"), { js_code }); + """.trimIndent() + } + + val init = + """ + + const wasmExports = wasmInstance.exports; + wasmExports.__init(); + wasmExports.startUnitTests?.(); + + """.trimIndent() + + val export = when (kind) { + WasmLoaderKind.D8, WasmLoaderKind.BROWSER -> + "export default wasmExports;\n" + + WasmLoaderKind.NODE -> + "module.exports = wasmExports;\n" + } + + return externalJs + instantiation + init + export +} + +fun writeCompilationResult( + result: WasmCompilerResult, + dir: File, + loaderKind: WasmLoaderKind, + fileNameBase: String = "index", +) { + dir.mkdirs() + File(dir, "$fileNameBase.wat").writeText(result.wat) + File(dir, "$fileNameBase.wasm").writeBytes(result.wasm) + val jsWithLoader = generateJsWasmLoader(loaderKind, "./$fileNameBase.wasm", result.js) + File(dir, "$fileNameBase.js").writeText(jsWithLoader) +} \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt index 4ff419388e6..f8893065d97 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.js.config.JSConfigurationKeys -internal fun eliminateDeadDeclarations(modules: List, context: WasmBackendContext) { +fun eliminateDeadDeclarations(modules: List, context: WasmBackendContext) { val printReachabilityInfo = context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) || java.lang.Boolean.getBoolean("kotlin.wasm.dce.print.reachability.info") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt index 1f120626e78..18dad148cd8 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt @@ -364,7 +364,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT val jsCode = buildString { append("(f) => (") appendParameterList(arity) - append(") => wasmInstance.exports.__callFunction_") + append(") => wasmExports.__callFunction_") append(info.hashString) append("(f, ") appendParameterList(arity) diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index 3169c802ecc..938f3bd7996 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -39,7 +39,7 @@ where advanced options include: -Xwasm Use experimental WebAssembly compiler backend -Xwasm-debug-info Add debug info to WebAssembly compiled module -Xwasm-kclass-fqn Enable support for FQ names in KClass - -Xwasm-launcher=esm|nodejs Picks flavor for the wasm launcher. Default is ESM. + -Xwasm-launcher=esm|nodejs|d8 Picks flavor for the wasm launcher. Default is ESM. -Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info -Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type -Xbuiltins-from-sources Compile builtIns from sources diff --git a/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt b/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt index 318497460be..329094878ae 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt @@ -241,6 +241,14 @@ fun box(): String { return "OK" } +// TODO: Rewrite test to use module system +@JsFun("() => { globalThis.main = wasmExports; }") +external fun hackNonModuleExport() + +fun main() { + hackNonModuleExport() +} + // FILE: functionTypes__after.js const exportedFres = main.exportedF()(1, 20, 300)("<", ">"); diff --git a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt index 6b48f67016a..7f2e65d43d8 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR, JS // MODULE: main // FILE: externals.kt @@ -25,6 +26,15 @@ fun anyAsEI(any: Any): EI = any as EI fun box(): String = "OK" +// TODO: Rewrite test to use module system + +@JsFun("() => { globalThis.main = wasmExports; }") +external fun hackNonModuleExport() + +fun main() { + hackNonModuleExport() +} + // FILE: jsExport__after.js const c = main.makeC(300); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt index 24a699c403d..010bf48a198 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt @@ -11,19 +11,15 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiManager 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.compileWasm -import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr -import org.jetbrains.kotlin.backend.wasm.wasmPhases +import org.jetbrains.kotlin.backend.wasm.* +import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.* 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.utils.sanitizeName import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.facade.TranslationUnit @@ -50,31 +46,17 @@ abstract class BasicWasmBoxTest( private val COMMON_FILES_NAME = "_common" - @Suppress("UNUSED_PARAMETER") - fun doTestWithCoroutinesPackageReplacement(filePath: String, coroutinesPackage: String) { - TODO("TestWithCoroutinesPackageReplacement are not supported") - } - fun doTest(filePath: String) = doTestWithTransformer(filePath) { it } fun doTestWithTransformer(filePath: String, transformer: java.util.function.Function) { val file = File(filePath) - val outputDir = getOutputDir(file) + val outputDirBase = File(getOutputDir(file), getTestName(true)) val fileContent = transformer.apply(KtTestUtil.doLoadFile(file)) TestFileFactoryImpl().use { testFactory -> val inputFiles: MutableList = TestFiles.createTestFiles(file.name, fileContent, testFactory, true) val testPackage = testFactory.testPackage - val outputFileBase = outputDir.absolutePath + "/" + getTestName(true) - val outputWatFile = File("$outputFileBase.wat") - val outputWasmFile = File("$outputFileBase.wasm") - val outputJsFile = File("$outputFileBase.js") - val outputBrowserDir = File("$outputFileBase.browser") - val outputFileNoDceBase = "${outputFileBase}NoDce" - val outputWatNoDceFile = File("$outputFileNoDceBase.wat") - val outputWasmNoDceFile = File("$outputFileNoDceBase.wasm") - val outputJsNoDceFile = File("$outputFileNoDceBase.js") - val outputBrowserNoDceDir = File("$outputFileNoDceBase.browser") + val languageVersionSettings = inputFiles.firstNotNullOfOrNull { it.languageVersionSettings } val kotlinFiles = mutableListOf() @@ -112,12 +94,9 @@ abstract class BasicWasmBoxTest( 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") + val dumpOutputDir = File(outputDirBase, "irdump") + println("\n ------ Dumping phases to file://${dumpOutputDir.absolutePath}") println(" ------ KT file://${file.absolutePath}") - println(" ------ WAT file://$outputWatFile") - println(" ------ WASM file://$outputWasmFile") - println(" ------ JS file://$outputJsFile") PhaseConfig( wasmPhases, dumpToDirectory = dumpOutputDir.path, @@ -139,33 +118,108 @@ abstract class BasicWasmBoxTest( AnalyzerWithCompilerReport(config.configuration) ) - compileAndRun( + val (allModules, backendContext) = compileToLoweredIr( + depsDescriptors = sourceModule, phaseConfig = phaseConfig, - sourceModule = sourceModule, - testPackage = testPackage, - dceEnabled = false, - outputWatFile = outputWatNoDceFile, - outputWasmFile = outputWasmNoDceFile, - outputJsFile = outputJsNoDceFile, - outputBrowserDir = outputBrowserNoDceDir, - debugMode = debugMode, - jsFilesBefore = jsFilesBefore, - jsFilesAfter = jsFilesAfter + irFactory = IrFactoryImpl, + exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, TEST_FUNCTION))), + propertyLazyInitialization = true, ) - compileAndRun( - phaseConfig = phaseConfig, - sourceModule = sourceModule, - testPackage = testPackage, - dceEnabled = true, - outputBrowserDir = outputBrowserDir, - debugMode = debugMode, - outputWatFile = outputWatFile, - outputWasmFile = outputWasmFile, - outputJsFile = outputJsFile, - jsFilesBefore = jsFilesBefore, - jsFilesAfter = jsFilesAfter + val compilerResult = compileWasm( + allModules = allModules, + backendContext = backendContext, + emitNameSection = true, + allowIncompleteImplementations = false, ) + + eliminateDeadDeclarations(allModules, backendContext) + + val compilerResultWithDCE = compileWasm( + allModules = allModules, + backendContext = backendContext, + emitNameSection = true, + allowIncompleteImplementations = true, + ) + + val testJsQuiet = """ + import exports from './index.js'; + + let actualResult + try { + actualResult = exports.box(); + } catch(e) { + console.log('Failed with exception!') + console.log('Message: ' + e.message) + console.log('Name: ' + e.name) + console.log('Stack:') + console.log(e.stack) + } + if (actualResult !== "OK") + throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`; + """.trimIndent() + + val testJsVerbose = testJsQuiet + """ + console.log('test passed'); + """.trimIndent() + + val testJs = if (debugMode >= DebugMode.DEBUG) testJsVerbose else testJsQuiet + + fun compileAndRunD8Test(name: String, res: WasmCompilerResult) { + val dir = File(outputDirBase, name) + if (debugMode >= DebugMode.DEBUG) { + val path = dir.absolutePath + println(" ------ $name WAT file://$path/index.wat") + println(" ------ $name WASM file://$path/index.wasm") + println(" ------ $name JS file://$path/index.js") + println(" ------ $name Test file://$path/test.js") + } + + writeCompilationResult(res, dir, WasmLoaderKind.D8) + File(dir, "test.js").writeText(testJs) + ExternalTool(System.getProperty("javascript.engine.path.V8")) + .run( + "--experimental-wasm-typed-funcref", + "--experimental-wasm-gc", + "--experimental-wasm-eh", + *jsFilesBefore.map { File(it).absolutePath }.toTypedArray(), + "--module", + "./test.js", + *jsFilesAfter.map { File(it).absolutePath }.toTypedArray(), + workingDirectory = dir + ) + } + + compileAndRunD8Test("d8", compilerResult) + compileAndRunD8Test("d8-dce", compilerResultWithDCE) + + if (debugMode >= DebugMode.SUPER_DEBUG) { + fun writeBrowserTest(name: String, res: WasmCompilerResult) { + val dir = File(outputDirBase, name) + writeCompilationResult(res, dir, WasmLoaderKind.BROWSER) + File(dir, "test.js").writeText(testJsVerbose) + File(dir, "index.html").writeText( + """ + + + + + + + """.trimIndent() + ) + val path = dir.absolutePath + println(" ------ $name WAT file://$path/index.wat") + println(" ------ $name WASM file://$path/index.wasm") + println(" ------ $name JS file://$path/index.js") + println(" ------ $name TEST file://$path/test.js") + println(" ------ $name HTML file://$path/index.html") + } + + writeBrowserTest("browser", compilerResult) + writeBrowserTest("browser-dce", compilerResultWithDCE) + } + } } @@ -178,108 +232,6 @@ abstract class BasicWasmBoxTest( .fold(testGroupOutputDir, ::File) } - private fun compileAndRun( - phaseConfig: PhaseConfig, - sourceModule: ModulesStructure, - testPackage: String?, - dceEnabled: Boolean, - outputWatFile: File, - outputWasmFile: File, - outputJsFile: File, - outputBrowserDir: File, - debugMode: DebugMode, - jsFilesBefore: List, - jsFilesAfter: List, - ) { - val (allModules, backendContext) = compileToLoweredIr( - depsDescriptors = sourceModule, - phaseConfig = phaseConfig, - irFactory = IrFactoryImpl, - exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, TEST_FUNCTION))), - propertyLazyInitialization = true, - ) - - val compilerResult = compileWasm( - allModules = allModules, - backendContext = backendContext, - emitNameSection = true, - dceEnabled = dceEnabled, - ) - - outputWatFile.write(compilerResult.wat) - outputWasmFile.writeBytes(compilerResult.wasm) - - val testRunner = """ - const wasmBinary = read(String.raw`${outputWasmFile.absoluteFile}`, 'binary'); - const wasmModule = new WebAssembly.Module(wasmBinary); - wasmInstance = new WebAssembly.Instance(wasmModule, { js_code }); - const ${sanitizeName(TEST_MODULE)} = wasmInstance.exports; - ${createJsRun(wasmInstance = "wasmInstance", dceEnabled = dceEnabled)} - """.trimIndent() - outputJsFile.write(compilerResult.js + "\n" + testRunner) - - if (debugMode >= DebugMode.SUPER_DEBUG) { - 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 createJsRun(wasmInstance: String, dceEnabled: Boolean) = """ - let actualResult - try { - $wasmInstance.exports.__init(); - $wasmInstance.exports.startUnitTests?.(); - actualResult = $wasmInstance.exports.$TEST_FUNCTION(); - } catch(e) { - console.log('Failed with exception!') - console.log('Message: ' + e.message) - console.log('Name: ' + e.name) - console.log('Stack:') - console.log(e.stack) - } - if (actualResult !== "OK") - throw `Wrong box result '${'$'}{actualResult}' (with DCE=${dceEnabled}); Expected "OK"`; - """.trimIndent() - - private fun createDirectoryToRunInBrowser(directory: File, compilerResult: WasmCompilerResult, dceEnabled: Boolean) { - val browserRunner = - """ - const response = await fetch("index.wasm"); - const wasmBinary = await response.arrayBuffer(); - wasmInstance = (await WebAssembly.instantiate(wasmBinary, { js_code })).instance; - ${createJsRun(wasmInstance = "wasmInstance", dceEnabled = dceEnabled)} - console.log("Test passed!"); - """.trimIndent() - - directory.mkdirs() - - File(directory, "index.html").writeText( - """ - - - - - - - """.trimIndent() - ) - File(directory, "index.js").writeText( - compilerResult.js + "\n" + browserRunner - ) - File(directory, "index.wasm").writeBytes( - compilerResult.wasm - ) - } - private fun createConfig(languageVersionSettings: LanguageVersionSettings?): JsConfig { val configuration = environment.configuration.copy() configuration.put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/engines/SpiderMonkey.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/engines/SpiderMonkey.kt index 43f3780429b..da972466d00 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/engines/SpiderMonkey.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/engines/SpiderMonkey.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.js.testOld.engines import java.io.BufferedReader +import java.io.File import java.io.InputStreamReader import java.lang.Boolean.getBoolean import kotlin.test.fail @@ -14,15 +15,27 @@ import kotlin.test.fail val toolLogsEnabled: Boolean = getBoolean("kotlin.js.test.verbose") class ExternalTool(val path: String) { - fun run(vararg arguments: String) { + fun run(vararg arguments: String, workingDirectory: File? = null) { val command = arrayOf(path, *arguments) - val process = ProcessBuilder(*command) + val processBuilder = ProcessBuilder(*command) .redirectErrorStream(true) - .start() + + if (workingDirectory != null) { + processBuilder.directory(workingDirectory) + } + + val process = processBuilder.start() + val commandString = command.joinToString(" ") { escapeShellArgument(it) } if (toolLogsEnabled) { - println(commandString) + println( + if (workingDirectory != null) { + "(cd '$workingDirectory' && $commandString)" + } else { + commandString + } + ) } // Print process output diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt index 60d0f275b05..bfd1a8d667b 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt @@ -139,8 +139,8 @@ internal fun anyToExternRef(x: Any): ExternalInterfaceType { } @JsFun("""(addr) => { - const mem16 = new Uint16Array(wasmInstance.exports.memory.buffer); - const mem32 = new Int32Array(wasmInstance.exports.memory.buffer); + const mem16 = new Uint16Array(wasmExports.memory.buffer); + const mem32 = new Int32Array(wasmExports.memory.buffer); const len = mem32[addr / 4]; const str_start_addr = (addr + 4) / 2; const slice = mem16.slice(str_start_addr, str_start_addr + len); @@ -163,7 +163,7 @@ internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String { //language=js @JsFun( """ (str, addr) => { - const memory = new DataView(wasmInstance.exports.memory.buffer); + const memory = new DataView(wasmExports.memory.buffer); for (var i = 0; i < str.length; i++) { memory.setInt16(addr + i * 2, str.charCodeAt(i), true); } diff --git a/libraries/stdlib/wasm/src/kotlin/io.kt b/libraries/stdlib/wasm/src/kotlin/io.kt index 16ce6fe27d7..6e85e8de284 100644 --- a/libraries/stdlib/wasm/src/kotlin/io.kt +++ b/libraries/stdlib/wasm/src/kotlin/io.kt @@ -7,11 +7,11 @@ package kotlin.io import kotlin.wasm.internal.* -@JsFun("(error) => console.error(\">>> \" + error)") +@JsFun("(error) => console.error(error)") internal external fun printError(error: String?): Unit -@JsFun("(message) => console.log(\">>> \" + message)") -private external fun printlnImpl(error: String?): Unit +@JsFun("(message) => console.log(message)") +private external fun printlnImpl(message: String?): Unit /** Prints the line separator to the standard output stream. */ public actual fun println() {