[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 <svyatoslav.kuzmich@jetbrains.com>
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 0c4f47b..fd1d9e5 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 @@
@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 2dfcfe4..00431d2 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 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.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 @@
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 ea0eda7..a995685 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 @@
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.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 @@
allModules: List<IrModuleFragment>,
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 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 4ff4193..f889306 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.acceptVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
-internal fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, context: WasmBackendContext) {
+fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, 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 1f12062..18dad14 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 @@
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 3169c80..938f3bd 100644
--- a/compiler/testData/cli/js/jsExtraHelp.out
+++ b/compiler/testData/cli/js/jsExtraHelp.out
@@ -39,7 +39,7 @@
-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 3184974..3290948 100644
--- a/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt
+++ b/compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt
@@ -241,6 +241,14 @@
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 6b48f67..7f2e65d 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 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 24a699c..010bf48 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.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 @@
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<String, String>) {
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<TestFile> = 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<String>()
@@ -112,12 +94,9 @@
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 @@
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(
+ """
+ <!DOCTYPE html>
+ <html lang="en">
+ <body>
+ <script src="test.js" type="module"></script>
+ </body>
+ </html>
+ """.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 @@
.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<String>,
- jsFilesAfter: List<String>,
- ) {
- 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(
- """
- <!DOCTYPE html>
- <html lang="en">
- <body>
- <script src="index.js" type="module"></script>
- </body>
- </html>
- """.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 43f3780..da97246 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 @@
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 60d0f27..bfd1a8d 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 @@
}
@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 @@
//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 16ce6fe..6e85e8d 100644
--- a/libraries/stdlib/wasm/src/kotlin/io.kt
+++ b/libraries/stdlib/wasm/src/kotlin/io.kt
@@ -7,11 +7,11 @@
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() {