[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>
This commit is contained in:
committed by
Space
parent
918a91dbdf
commit
4636f71076
+1
-1
@@ -242,7 +242,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
|||||||
|
|
||||||
@Argument(
|
@Argument(
|
||||||
value = "-Xwasm-launcher",
|
value = "-Xwasm-launcher",
|
||||||
valueDescription = "esm|nodejs",
|
valueDescription = "esm|nodejs|d8",
|
||||||
description = "Picks flavor for the wasm launcher. Default is ESM."
|
description = "Picks flavor for the wasm launcher. Default is ESM."
|
||||||
)
|
)
|
||||||
var wasmLauncher: String? by NullableStringFreezableVar("esm")
|
var wasmLauncher: String? by NullableStringFreezableVar("esm")
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import com.intellij.openapi.util.text.StringUtil
|
|||||||
import org.jetbrains.kotlin.backend.common.CompilationException
|
import org.jetbrains.kotlin.backend.common.CompilationException
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||||
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
|
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.compileWasm
|
||||||
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
|
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
|
||||||
import org.jetbrains.kotlin.backend.wasm.wasmPhases
|
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.*
|
||||||
import org.jetbrains.kotlin.cli.common.ExitCode.*
|
import org.jetbrains.kotlin.cli.common.ExitCode.*
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
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.IncrementalDataProvider
|
||||||
import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker
|
import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker
|
||||||
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
|
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.*
|
||||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCaches
|
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCaches
|
||||||
@@ -326,49 +329,30 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
|||||||
exportedDeclarations = setOf(FqName("main")),
|
exportedDeclarations = setOf(FqName("main")),
|
||||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||||
)
|
)
|
||||||
|
if (arguments.irDce) {
|
||||||
|
eliminateDeadDeclarations(allModules, backendContext)
|
||||||
|
}
|
||||||
val res = compileWasm(
|
val res = compileWasm(
|
||||||
allModules = allModules,
|
allModules = allModules,
|
||||||
backendContext = backendContext,
|
backendContext = backendContext,
|
||||||
emitNameSection = arguments.wasmDebug,
|
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 = """
|
val launcherKind = when (arguments.wasmLauncher) {
|
||||||
export default WebAssembly.instantiateStreaming(fetch('${outputWasmFile.name}'), { runtime, js_code }).then((it) => {
|
"esm" -> WasmLoaderKind.BROWSER
|
||||||
wasmInstance = it.instance;
|
"nodejs" -> WasmLoaderKind.NODE
|
||||||
wasmInstance.exports.__init?.();
|
"d8" -> WasmLoaderKind.D8
|
||||||
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
|
|
||||||
else -> throw IllegalArgumentException("Unrecognized flavor for the wasm launcher")
|
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
|
return OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.PhaseConfig
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
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.WasmCompiledModuleFragment
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
||||||
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
|
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.WasmIrToBinary
|
||||||
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
|
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray)
|
class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray)
|
||||||
|
|
||||||
@@ -75,15 +75,10 @@ fun compileWasm(
|
|||||||
allModules: List<IrModuleFragment>,
|
allModules: List<IrModuleFragment>,
|
||||||
backendContext: WasmBackendContext,
|
backendContext: WasmBackendContext,
|
||||||
emitNameSection: Boolean = false,
|
emitNameSection: Boolean = false,
|
||||||
dceEnabled: Boolean = false,
|
allowIncompleteImplementations: Boolean = false,
|
||||||
): WasmCompilerResult {
|
): WasmCompilerResult {
|
||||||
|
|
||||||
if (dceEnabled) {
|
|
||||||
eliminateDeadDeclarations(allModules, backendContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
|
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
|
||||||
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = dceEnabled)
|
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
|
||||||
allModules.forEach { codeGenerator.generateModule(it) }
|
allModules.forEach { codeGenerator.generateModule(it) }
|
||||||
|
|
||||||
val linkedModule = compiledWasmModule.linkWasmCompiledFragments()
|
val linkedModule = compiledWasmModule.linkWasmCompiledFragments()
|
||||||
@@ -107,13 +102,76 @@ fun compileWasm(
|
|||||||
fun WasmCompiledModuleFragment.generateJs(): String {
|
fun WasmCompiledModuleFragment.generateJs(): String {
|
||||||
//language=js
|
//language=js
|
||||||
val runtime = """
|
val runtime = """
|
||||||
var wasmInstance = null;
|
|
||||||
|
|
||||||
const externrefBoxes = new WeakMap();
|
const externrefBoxes = new WeakMap();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val jsFuns = jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode }
|
val jsCodeBody = jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode }
|
||||||
val jsCode = "\nconst js_code = {$jsFuns};"
|
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
|
||||||
|
val jsCode =
|
||||||
|
"\nconst js_code = {\n$jsCodeBodyIndented\n};\n"
|
||||||
|
|
||||||
return runtime + jsCode
|
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)
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
|||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||||
|
|
||||||
internal fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, context: WasmBackendContext) {
|
fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, context: WasmBackendContext) {
|
||||||
val printReachabilityInfo =
|
val printReachabilityInfo =
|
||||||
context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) ||
|
context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) ||
|
||||||
java.lang.Boolean.getBoolean("kotlin.wasm.dce.print.reachability.info")
|
java.lang.Boolean.getBoolean("kotlin.wasm.dce.print.reachability.info")
|
||||||
|
|||||||
+1
-1
@@ -364,7 +364,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
val jsCode = buildString {
|
val jsCode = buildString {
|
||||||
append("(f) => (")
|
append("(f) => (")
|
||||||
appendParameterList(arity)
|
appendParameterList(arity)
|
||||||
append(") => wasmInstance.exports.__callFunction_")
|
append(") => wasmExports.__callFunction_")
|
||||||
append(info.hashString)
|
append(info.hashString)
|
||||||
append("(f, ")
|
append("(f, ")
|
||||||
appendParameterList(arity)
|
appendParameterList(arity)
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ where advanced options include:
|
|||||||
-Xwasm Use experimental WebAssembly compiler backend
|
-Xwasm Use experimental WebAssembly compiler backend
|
||||||
-Xwasm-debug-info Add debug info to WebAssembly compiled module
|
-Xwasm-debug-info Add debug info to WebAssembly compiled module
|
||||||
-Xwasm-kclass-fqn Enable support for FQ names in KClass
|
-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-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
|
-Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type
|
||||||
-Xbuiltins-from-sources Compile builtIns from sources
|
-Xbuiltins-from-sources Compile builtIns from sources
|
||||||
|
|||||||
@@ -241,6 +241,14 @@ fun box(): String {
|
|||||||
return "OK"
|
return "OK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rewrite test to use module system
|
||||||
|
@JsFun("() => { globalThis.main = wasmExports; }")
|
||||||
|
external fun hackNonModuleExport()
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
hackNonModuleExport()
|
||||||
|
}
|
||||||
|
|
||||||
// FILE: functionTypes__after.js
|
// FILE: functionTypes__after.js
|
||||||
|
|
||||||
const exportedFres = main.exportedF()(1, 20, 300)("<", ">");
|
const exportedFres = main.exportedF()(1, 20, 300)("<", ">");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// IGNORE_BACKEND: JS_IR, JS
|
||||||
// MODULE: main
|
// MODULE: main
|
||||||
// FILE: externals.kt
|
// FILE: externals.kt
|
||||||
|
|
||||||
@@ -25,6 +26,15 @@ fun anyAsEI(any: Any): EI = any as EI
|
|||||||
|
|
||||||
fun box(): String = "OK"
|
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
|
// FILE: jsExport__after.js
|
||||||
|
|
||||||
const c = main.makeC(300);
|
const c = main.makeC(300);
|
||||||
|
|||||||
@@ -11,19 +11,15 @@ import com.intellij.openapi.vfs.VirtualFileManager
|
|||||||
import com.intellij.psi.PsiManager
|
import com.intellij.psi.PsiManager
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap
|
import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap
|
||||||
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
|
import org.jetbrains.kotlin.backend.wasm.*
|
||||||
import org.jetbrains.kotlin.backend.wasm.compileWasm
|
import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations
|
||||||
import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr
|
|
||||||
import org.jetbrains.kotlin.backend.wasm.wasmPhases
|
|
||||||
import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings
|
import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.config.*
|
import org.jetbrains.kotlin.config.*
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
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.prepareAnalyzedSourceModule
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
import org.jetbrains.kotlin.js.config.JsConfig
|
||||||
import org.jetbrains.kotlin.js.facade.TranslationUnit
|
import org.jetbrains.kotlin.js.facade.TranslationUnit
|
||||||
@@ -50,31 +46,17 @@ abstract class BasicWasmBoxTest(
|
|||||||
|
|
||||||
private val COMMON_FILES_NAME = "_common"
|
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 doTest(filePath: String) = doTestWithTransformer(filePath) { it }
|
||||||
fun doTestWithTransformer(filePath: String, transformer: java.util.function.Function<String, String>) {
|
fun doTestWithTransformer(filePath: String, transformer: java.util.function.Function<String, String>) {
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
||||||
|
|
||||||
val outputDir = getOutputDir(file)
|
val outputDirBase = File(getOutputDir(file), getTestName(true))
|
||||||
val fileContent = transformer.apply(KtTestUtil.doLoadFile(file))
|
val fileContent = transformer.apply(KtTestUtil.doLoadFile(file))
|
||||||
|
|
||||||
TestFileFactoryImpl().use { testFactory ->
|
TestFileFactoryImpl().use { testFactory ->
|
||||||
val inputFiles: MutableList<TestFile> = TestFiles.createTestFiles(file.name, fileContent, testFactory, true)
|
val inputFiles: MutableList<TestFile> = TestFiles.createTestFiles(file.name, fileContent, testFactory, true)
|
||||||
val testPackage = testFactory.testPackage
|
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 languageVersionSettings = inputFiles.firstNotNullOfOrNull { it.languageVersionSettings }
|
||||||
|
|
||||||
val kotlinFiles = mutableListOf<String>()
|
val kotlinFiles = mutableListOf<String>()
|
||||||
@@ -112,12 +94,9 @@ abstract class BasicWasmBoxTest(
|
|||||||
|
|
||||||
val phaseConfig = if (debugMode >= DebugMode.DEBUG) {
|
val phaseConfig = if (debugMode >= DebugMode.DEBUG) {
|
||||||
val allPhasesSet = if (debugMode >= DebugMode.SUPER_DEBUG) wasmPhases.toPhaseMap().values.toSet() else emptySet()
|
val allPhasesSet = if (debugMode >= DebugMode.SUPER_DEBUG) wasmPhases.toPhaseMap().values.toSet() else emptySet()
|
||||||
val dumpOutputDir = File(outputWatFile.parent, outputWatFile.nameWithoutExtension + "-irdump")
|
val dumpOutputDir = File(outputDirBase, "irdump")
|
||||||
println("\n ------ Dumping phases to file://$dumpOutputDir")
|
println("\n ------ Dumping phases to file://${dumpOutputDir.absolutePath}")
|
||||||
println(" ------ KT file://${file.absolutePath}")
|
println(" ------ KT file://${file.absolutePath}")
|
||||||
println(" ------ WAT file://$outputWatFile")
|
|
||||||
println(" ------ WASM file://$outputWasmFile")
|
|
||||||
println(" ------ JS file://$outputJsFile")
|
|
||||||
PhaseConfig(
|
PhaseConfig(
|
||||||
wasmPhases,
|
wasmPhases,
|
||||||
dumpToDirectory = dumpOutputDir.path,
|
dumpToDirectory = dumpOutputDir.path,
|
||||||
@@ -139,33 +118,108 @@ abstract class BasicWasmBoxTest(
|
|||||||
AnalyzerWithCompilerReport(config.configuration)
|
AnalyzerWithCompilerReport(config.configuration)
|
||||||
)
|
)
|
||||||
|
|
||||||
compileAndRun(
|
val (allModules, backendContext) = compileToLoweredIr(
|
||||||
|
depsDescriptors = sourceModule,
|
||||||
phaseConfig = phaseConfig,
|
phaseConfig = phaseConfig,
|
||||||
sourceModule = sourceModule,
|
irFactory = IrFactoryImpl,
|
||||||
testPackage = testPackage,
|
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, TEST_FUNCTION))),
|
||||||
dceEnabled = false,
|
propertyLazyInitialization = true,
|
||||||
outputWatFile = outputWatNoDceFile,
|
|
||||||
outputWasmFile = outputWasmNoDceFile,
|
|
||||||
outputJsFile = outputJsNoDceFile,
|
|
||||||
outputBrowserDir = outputBrowserNoDceDir,
|
|
||||||
debugMode = debugMode,
|
|
||||||
jsFilesBefore = jsFilesBefore,
|
|
||||||
jsFilesAfter = jsFilesAfter
|
|
||||||
)
|
)
|
||||||
|
|
||||||
compileAndRun(
|
val compilerResult = compileWasm(
|
||||||
phaseConfig = phaseConfig,
|
allModules = allModules,
|
||||||
sourceModule = sourceModule,
|
backendContext = backendContext,
|
||||||
testPackage = testPackage,
|
emitNameSection = true,
|
||||||
dceEnabled = true,
|
allowIncompleteImplementations = false,
|
||||||
outputBrowserDir = outputBrowserDir,
|
|
||||||
debugMode = debugMode,
|
|
||||||
outputWatFile = outputWatFile,
|
|
||||||
outputWasmFile = outputWasmFile,
|
|
||||||
outputJsFile = outputJsFile,
|
|
||||||
jsFilesBefore = jsFilesBefore,
|
|
||||||
jsFilesAfter = jsFilesAfter
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 @@ abstract class BasicWasmBoxTest(
|
|||||||
.fold(testGroupOutputDir, ::File)
|
.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 {
|
private fun createConfig(languageVersionSettings: LanguageVersionSettings?): JsConfig {
|
||||||
val configuration = environment.configuration.copy()
|
val configuration = environment.configuration.copy()
|
||||||
configuration.put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE)
|
configuration.put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.js.testOld.engines
|
package org.jetbrains.kotlin.js.testOld.engines
|
||||||
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.lang.Boolean.getBoolean
|
import java.lang.Boolean.getBoolean
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
@@ -14,15 +15,27 @@ import kotlin.test.fail
|
|||||||
val toolLogsEnabled: Boolean = getBoolean("kotlin.js.test.verbose")
|
val toolLogsEnabled: Boolean = getBoolean("kotlin.js.test.verbose")
|
||||||
|
|
||||||
class ExternalTool(val path: String) {
|
class ExternalTool(val path: String) {
|
||||||
fun run(vararg arguments: String) {
|
fun run(vararg arguments: String, workingDirectory: File? = null) {
|
||||||
val command = arrayOf(path, *arguments)
|
val command = arrayOf(path, *arguments)
|
||||||
val process = ProcessBuilder(*command)
|
val processBuilder = ProcessBuilder(*command)
|
||||||
.redirectErrorStream(true)
|
.redirectErrorStream(true)
|
||||||
.start()
|
|
||||||
|
if (workingDirectory != null) {
|
||||||
|
processBuilder.directory(workingDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
val process = processBuilder.start()
|
||||||
|
|
||||||
|
|
||||||
val commandString = command.joinToString(" ") { escapeShellArgument(it) }
|
val commandString = command.joinToString(" ") { escapeShellArgument(it) }
|
||||||
if (toolLogsEnabled) {
|
if (toolLogsEnabled) {
|
||||||
println(commandString)
|
println(
|
||||||
|
if (workingDirectory != null) {
|
||||||
|
"(cd '$workingDirectory' && $commandString)"
|
||||||
|
} else {
|
||||||
|
commandString
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print process output
|
// Print process output
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ internal fun anyToExternRef(x: Any): ExternalInterfaceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsFun("""(addr) => {
|
@JsFun("""(addr) => {
|
||||||
const mem16 = new Uint16Array(wasmInstance.exports.memory.buffer);
|
const mem16 = new Uint16Array(wasmExports.memory.buffer);
|
||||||
const mem32 = new Int32Array(wasmInstance.exports.memory.buffer);
|
const mem32 = new Int32Array(wasmExports.memory.buffer);
|
||||||
const len = mem32[addr / 4];
|
const len = mem32[addr / 4];
|
||||||
const str_start_addr = (addr + 4) / 2;
|
const str_start_addr = (addr + 4) / 2;
|
||||||
const slice = mem16.slice(str_start_addr, str_start_addr + len);
|
const slice = mem16.slice(str_start_addr, str_start_addr + len);
|
||||||
@@ -163,7 +163,7 @@ internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String {
|
|||||||
//language=js
|
//language=js
|
||||||
@JsFun(
|
@JsFun(
|
||||||
""" (str, addr) => {
|
""" (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++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
memory.setInt16(addr + i * 2, str.charCodeAt(i), true);
|
memory.setInt16(addr + i * 2, str.charCodeAt(i), true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ package kotlin.io
|
|||||||
|
|
||||||
import kotlin.wasm.internal.*
|
import kotlin.wasm.internal.*
|
||||||
|
|
||||||
@JsFun("(error) => console.error(\">>> \" + error)")
|
@JsFun("(error) => console.error(error)")
|
||||||
internal external fun printError(error: String?): Unit
|
internal external fun printError(error: String?): Unit
|
||||||
|
|
||||||
@JsFun("(message) => console.log(\">>> \" + message)")
|
@JsFun("(message) => console.log(message)")
|
||||||
private external fun printlnImpl(error: String?): Unit
|
private external fun printlnImpl(message: String?): Unit
|
||||||
|
|
||||||
/** Prints the line separator to the standard output stream. */
|
/** Prints the line separator to the standard output stream. */
|
||||||
public actual fun println() {
|
public actual fun println() {
|
||||||
|
|||||||
Reference in New Issue
Block a user