[K/Wasm] Introduce stepping tests for Wasm

This commit is contained in:
Artem Kobzar
2023-08-15 17:03:11 +00:00
committed by Space Team
parent 577d0db3f1
commit 05ed134fbb
120 changed files with 4035 additions and 2359 deletions
@@ -55,6 +55,10 @@ fun main(args: Array<String>) {
testClass<AbstractFirWasmCodegenWasmJsInteropTest> {
model("codegen/boxWasmJsInterop")
}
testClass<AbstractFirWasmSteppingTest> {
model("debug/stepping")
}
}
testGroup("wasm/wasm.tests/tests-gen", "js/js.translator/testData", testRunnerMethodName = "runTest0") {
@@ -84,6 +88,10 @@ fun main(args: Array<String>) {
testClass<AbstractK1WasmWasiCodegenBoxTest> {
model("codegen/boxWasmWasi")
}
testClass<AbstractK1WasmSteppingTest> {
model("debug/stepping")
}
}
}
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.test.builders.*
import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.frontend.fir.*
import org.jetbrains.kotlin.test.frontend.fir.handlers.*
import org.jetbrains.kotlin.test.model.*
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigura
import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibBackendFacade
import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade
import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner
import org.jetbrains.kotlin.wasm.test.handlers.WasmDebugRunner
open class AbstractFirWasmTest(
@@ -99,3 +101,18 @@ open class AbstractFirWasmJsTranslatorTest : AbstractFirWasmTest(
"js/js.translator/testData/box/",
"js.translator/firBox"
)
open class AbstractFirWasmSteppingTest : AbstractFirWasmTest(
"compiler/testData/debug/stepping/",
"debug/stepping/"
) {
override val wasmBoxTestRunner: Constructor<AnalysisHandler<BinaryArtifacts.Wasm>>
get() = ::WasmDebugRunner
override fun TestConfigurationBuilder.configuration() {
commonConfigurationForWasmBlackBoxCodegenTest()
defaultDirectives {
+WasmEnvironmentConfigurationDirectives.GENERATE_SOURCE_MAP
}
}
}
@@ -8,6 +8,9 @@ package org.jetbrains.kotlin.wasm.test
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.ir.IrBackendInput
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.builders.wasmArtifactsHandlersStep
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2IrConverter
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact
@@ -17,6 +20,7 @@ import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigura
import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibBackendFacade
import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade
import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner
import org.jetbrains.kotlin.wasm.test.handlers.WasmDebugRunner
abstract class AbstractK1WasmTest(
pathToTestDir: String,
@@ -62,3 +66,19 @@ open class AbstractK1WasmJsTranslatorTest : AbstractK1WasmTest(
"js/js.translator/testData/box/",
"js.translator/k1Box"
)
open class AbstractK1WasmSteppingTest : AbstractK1WasmTest(
"compiler/testData/debug/stepping/",
"debug/stepping/"
) {
override val wasmBoxTestRunner: Constructor<AnalysisHandler<BinaryArtifacts.Wasm>>
get() = ::WasmDebugRunner
override fun TestConfigurationBuilder.configuration() {
commonConfigurationForWasmBlackBoxCodegenTest()
defaultDirectives {
+WasmEnvironmentConfigurationDirectives.GENERATE_SOURCE_MAP
}
}
}
@@ -41,7 +41,7 @@ abstract class AbstractWasmBlackBoxCodegenTestBase<R : ResultingArtifact.Fronten
abstract val wasmEnvironmentConfigurator: Constructor<EnvironmentConfigurator>
open val additionalSourceProvider: Constructor<AdditionalSourceProvider>? = null
override fun TestConfigurationBuilder.configuration() {
protected fun TestConfigurationBuilder.commonConfigurationForWasmBlackBoxCodegenTest() {
globalDefaults {
frontend = targetFrontend
targetPlatform = WasmPlatforms.Default
@@ -56,16 +56,6 @@ abstract class AbstractWasmBlackBoxCodegenTestBase<R : ResultingArtifact.Fronten
WasmEnvironmentConfigurationDirectives.TEST_GROUP_OUTPUT_DIR_PREFIX with testGroupOutputDirPrefix
}
forTestsNotMatching("compiler/testData/codegen/box/diagnostics/functions/tailRecursion/*") {
defaultDirectives {
DIAGNOSTICS with "-warnings"
}
}
forTestsNotMatching("compiler/testData/codegen/boxError/*") {
enableMetaInfoHandler()
}
useConfigurators(
wasmEnvironmentConfigurator,
)
@@ -87,6 +77,7 @@ abstract class AbstractWasmBlackBoxCodegenTestBase<R : ResultingArtifact.Fronten
)
facadeStep(frontendFacade)
classicFrontendHandlersStep {
commonClassicFrontendHandlersForCodegenTest()
useHandlers(::ClassicDiagnosticsHandler)
@@ -103,6 +94,24 @@ abstract class AbstractWasmBlackBoxCodegenTestBase<R : ResultingArtifact.Fronten
klibArtifactsHandlersStep()
facadeStep(afterBackendFacade)
wasmArtifactsHandlersStep {
useHandlers(wasmBoxTestRunner)
}
}
override fun TestConfigurationBuilder.configuration() {
commonConfigurationForWasmBlackBoxCodegenTest()
forTestsNotMatching("compiler/testData/codegen/box/diagnostics/functions/tailRecursion/*") {
defaultDirectives {
DIAGNOSTICS with "-warnings"
}
}
forTestsNotMatching("compiler/testData/codegen/boxError/*") {
enableMetaInfoHandler()
}
forTestsMatching("compiler/testData/codegen/box/involvesIrInterpreter/*") {
enableMetaInfoHandler()
configureKlibArtifactsHandlersStep {
@@ -112,9 +121,5 @@ abstract class AbstractWasmBlackBoxCodegenTestBase<R : ResultingArtifact.Fronten
useHandlers(::WasmIrInterpreterDumpHandler)
}
}
wasmArtifactsHandlersStep {
useHandlers(wasmBoxTestRunner)
}
}
}
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.test.DebugMode
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.model.AbstractTestFacade
import org.jetbrains.kotlin.test.model.ArtifactKinds
import org.jetbrains.kotlin.test.model.BinaryArtifacts
@@ -43,6 +44,7 @@ class WasmBackendFacade(
override fun transform(module: TestModule, inputArtifact: BinaryArtifacts.KLib): BinaryArtifacts.Wasm? {
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val generateSourceMaps = WasmEnvironmentConfigurationDirectives.GENERATE_SOURCE_MAP in testServices.moduleStructure.allDirectives
// Enforce PL with the ERROR log level to fail any tests where PL detected any incompatibilities.
configuration.setupPartialLinkageConfig(PartialLinkageConfig(PartialLinkageMode.ENABLE, PartialLinkageLogLevel.ERROR))
@@ -104,6 +106,7 @@ class WasmBackendFacade(
emitNameSection = true,
allowIncompleteImplementations = false,
generateWat = generateWat,
generateSourceMaps = generateSourceMaps
)
val dceDumpNameCache = DceDumpNameCache()
@@ -118,6 +121,7 @@ class WasmBackendFacade(
emitNameSection = true,
allowIncompleteImplementations = true,
generateWat = generateWat,
generateSourceMaps = generateSourceMaps
)
return BinaryArtifacts.Wasm(
@@ -5,10 +5,15 @@
package org.jetbrains.kotlin.wasm.test.handlers
import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.test.backend.handlers.WasmBinaryArtifactHandler
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.moduleStructure
import java.io.File
abstract class AbstractWasmArtifactsCollector(testServices: TestServices) : WasmBinaryArtifactHandler(testServices) {
val modulesToArtifact = mutableMapOf<TestModule, BinaryArtifacts.Wasm>()
@@ -16,4 +21,95 @@ abstract class AbstractWasmArtifactsCollector(testServices: TestServices) : Wasm
override fun processModule(module: TestModule, info: BinaryArtifacts.Wasm) {
modulesToArtifact[module] = info
}
}
protected fun collectJsArtifacts(originalFile: File): JsArtifacts {
val jsFiles = mutableListOf<AdditionalFile>()
val mjsFiles = mutableListOf<AdditionalFile>()
var entryMjs: String? = "test.mjs"
testServices.moduleStructure.modules.forEach { m ->
m.files.forEach { file: TestFile ->
val name = file.name
when {
name.endsWith(".js") ->
jsFiles += AdditionalFile(file.name, file.originalContent)
name.endsWith(".mjs") -> {
mjsFiles += AdditionalFile(file.name, file.originalContent)
if (name == "entry.mjs") {
entryMjs = name
}
}
}
}
}
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_EXTENSION)
.takeIf { it.exists() }
?.let {
jsFiles += AdditionalFile(it.name, it.readText())
}
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_MODULE_EXTENSION)
.takeIf { it.exists() }
?.let {
mjsFiles += AdditionalFile(it.name, it.readText())
}
return JsArtifacts(entryMjs, jsFiles, mjsFiles)
}
protected fun JsArtifacts.saveJsArtifacts(baseDir: File): SavedJsArtifacts {
val mjsFilePaths = mutableListOf<String>()
for (mjsFile: AdditionalFile in mjsFiles) {
val file = File(baseDir, mjsFile.name)
file.writeText(mjsFile.content)
mjsFilePaths += file.canonicalPath
}
val jsFilePaths = mutableListOf<String>()
for (jsFile: AdditionalFile in jsFiles) {
val file = File(baseDir, jsFile.name)
file.writeText(jsFile.content)
jsFilePaths += file.canonicalPath
}
return SavedJsArtifacts(jsFilePaths, mjsFilePaths)
}
protected fun processExceptions(exceptions: List<Throwable>) {
when (exceptions.size) {
0 -> {} // Everything OK
1 -> {
throw exceptions.single()
}
else -> {
throw AssertionError("Failed with several exceptions. Look at suppressed exceptions below.").apply {
exceptions.forEach { addSuppressed(it) }
}
}
}
}
protected class AdditionalFile(val name: String, val content: String)
protected class JsArtifacts(val entryPath: String?, val jsFiles: List<AdditionalFile>, val mjsFiles: List<AdditionalFile>)
protected data class SavedJsArtifacts(val jsFilePaths: List<String>, val mjsFilePaths: List<String>)
}
fun TestServices.getWasmTestOutputDirectory(): File {
val originalFile = moduleStructure.originalTestDataFiles.first()
val allDirectives = moduleStructure.allDirectives
val pathToRootOutputDir = allDirectives[WasmEnvironmentConfigurationDirectives.PATH_TO_ROOT_OUTPUT_DIR].first()
val testGroupDirPrefix = allDirectives[WasmEnvironmentConfigurationDirectives.TEST_GROUP_OUTPUT_DIR_PREFIX].first()
val pathToTestDir = allDirectives[WasmEnvironmentConfigurationDirectives.PATH_TO_TEST_DIR].first()
val testGroupOutputDir = File(File(pathToRootOutputDir, "out"), testGroupDirPrefix)
val stopFile = File(pathToTestDir)
return generateSequence(originalFile.parentFile) { it.parentFile }
.takeWhile { it != stopFile }
.map { it.name }
.toList().asReversed()
.fold(testGroupOutputDir, ::File)
.let { File(it, originalFile.nameWithoutExtension) }
}
@@ -32,41 +32,8 @@ class WasmBoxRunner(
val artifacts = modulesToArtifact.values.single()
val baseFileName = "index"
val outputDirBase = testServices.getWasmTestOutputDirectory()
val jsFiles = mutableListOf<AdditionalFile>()
val mjsFiles = mutableListOf<AdditionalFile>()
var entryMjs: String? = "test.mjs"
testServices.moduleStructure.modules.forEach { m ->
m.files.forEach { file: TestFile ->
val name = file.name
when {
name.endsWith(".js") ->
jsFiles += AdditionalFile(file.name, file.originalContent)
name.endsWith(".mjs") -> {
mjsFiles += AdditionalFile(file.name, file.originalContent)
if (name == "entry.mjs") {
entryMjs = name
}
}
}
}
}
val originalFile = testServices.moduleStructure.originalTestDataFiles.first()
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_EXTENSION)
.takeIf { it.exists() }
?.let {
jsFiles += AdditionalFile(it.name, it.readText())
}
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_MODULE_EXTENSION)
.takeIf { it.exists() }
?.let {
mjsFiles += AdditionalFile(it.name, it.readText())
}
val collectedJsArtifacts = collectJsArtifacts(originalFile)
val debugMode = DebugMode.fromSystemProperty("kotlin.wasm.debugMode")
val startUnitTests = RUN_UNIT_TESTS in testServices.moduleStructure.allDirectives
@@ -106,16 +73,7 @@ class WasmBoxRunner(
writeCompilationResult(res, dir, baseFileName)
File(dir, "test.mjs").writeText(testJs)
for (mjsFile: AdditionalFile in mjsFiles) {
File(dir, mjsFile.name).writeText(mjsFile.content)
}
val jsFilePaths = mutableListOf<String>()
for (jsFile: AdditionalFile in jsFiles) {
val file = File(dir, jsFile.name)
file.writeText(jsFile.content)
jsFilePaths += file.canonicalPath
}
val (jsFilePaths) = collectedJsArtifacts.saveJsArtifacts(dir)
if (debugMode >= DebugMode.DEBUG) {
File(dir, "index.html").writeText(
@@ -149,7 +107,7 @@ class WasmBoxRunner(
println(" ------ $mode Test file://$path/test.mjs")
val projectName = "kotlin"
println(" ------ $mode HTML http://0.0.0.0:63342/$projectName/${dir.path}/index.html")
for (mjsFile: AdditionalFile in mjsFiles) {
for (mjsFile: AdditionalFile in collectedJsArtifacts.mjsFiles) {
println(" ------ $mode External ESM file://$path/${mjsFile.name}")
}
}
@@ -164,23 +122,13 @@ class WasmBoxRunner(
debugMode = debugMode,
disableExceptions = disableExceptions,
failsIn = failsIn,
entryMjs = entryMjs,
entryMjs = collectedJsArtifacts.entryPath,
jsFilePaths = jsFilePaths,
workingDirectory = dir,
)
}
when (exceptions.size) {
0 -> {} // Everything OK
1 -> {
throw exceptions.single()
}
else -> {
throw AssertionError("Failed with several exceptions. Look at suppressed exceptions below.").apply {
exceptions.forEach { addSuppressed(it) }
}
}
}
processExceptions(exceptions)
if (mode == "dce") {
checkExpectedOutputSize(debugMode, testFileText, dir)
@@ -190,8 +138,6 @@ class WasmBoxRunner(
writeToFilesAndRunTest("dev", artifacts.compilerResult)
writeToFilesAndRunTest("dce", artifacts.compilerResultWithDCE)
}
private class AdditionalFile(val name: String, val content: String)
}
internal fun WasmVM.runWithCathedExceptions(
@@ -223,24 +169,6 @@ internal fun WasmVM.runWithCathedExceptions(
return null
}
fun TestServices.getWasmTestOutputDirectory(): File {
val originalFile = moduleStructure.originalTestDataFiles.first()
val allDirectives = moduleStructure.allDirectives
val pathToRootOutputDir = allDirectives[WasmEnvironmentConfigurationDirectives.PATH_TO_ROOT_OUTPUT_DIR].first()
val testGroupDirPrefix = allDirectives[WasmEnvironmentConfigurationDirectives.TEST_GROUP_OUTPUT_DIR_PREFIX].first()
val pathToTestDir = allDirectives[WasmEnvironmentConfigurationDirectives.PATH_TO_TEST_DIR].first()
val testGroupOutputDir = File(File(pathToRootOutputDir, "out"), testGroupDirPrefix)
val stopFile = File(pathToTestDir)
return generateSequence(originalFile.parentFile) { it.parentFile }
.takeWhile { it != stopFile }
.map { it.name }
.toList().asReversed()
.fold(testGroupOutputDir, ::File)
.let { File(it, originalFile.nameWithoutExtension) }
}
fun checkExpectedOutputSize(debugMode: DebugMode, testFileContent: String, testDir: File) {
val expectedSizes =
InTextDirectivesUtils.findListWithPrefixes(testFileContent, "// WASM_DCE_EXPECTED_OUTPUT_SIZE: ")
@@ -0,0 +1,169 @@
/*
* 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.handlers
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.writeCompilationResult
import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.moduleStructure
import org.jetbrains.kotlin.test.utils.SteppingTestLoggedData
import org.jetbrains.kotlin.test.utils.checkSteppingTestResult
import org.jetbrains.kotlin.test.utils.formatAsSteppingTestExpectation
import org.jetbrains.kotlin.wasm.test.tools.WasmVM
import java.io.File
class WasmDebugRunner(testServices: TestServices) : AbstractWasmArtifactsCollector(testServices) {
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (!someAssertionWasFailed) {
saveAndRunWasmCode()
}
}
private fun saveAndRunWasmCode() {
val artifacts = modulesToArtifact.values.single()
val outputDirBase = testServices.getWasmTestOutputDirectory()
val originalFile = testServices.moduleStructure.originalTestDataFiles.first()
val mainModule = WasmEnvironmentConfigurator.getMainModule(testServices)
val collectedJsArtifacts = collectJsArtifacts(originalFile)
val testFileContent = """
let messageId = 0;
const locations = [];
function addLocation(frame) {
locations.push({
functionName: frame.functionName,
functionLine: frame.functionLocation?.lineNumber,
functionColumn: frame.functionLocation?.columnNumber,
line: frame.location.lineNumber,
column: frame.location.columnNumber
})
}
function sendMessage(message) { send(JSON.stringify(Object.assign(message, { id: messageId++ }))) }
function enableDebugger() { sendMessage({ method: 'Debugger.enable' }) }
function disableDebugger() { sendMessage({ method: 'Debugger.disable' }) }
function stepInto() { sendMessage({ method: "Debugger.stepInto" }) ;}
function setBreakpoint(func) {
const scriptId = %FunctionGetScriptId(func);
const offset = %FunctionGetScriptSourcePosition(func);
const loc = %ScriptLocationFromLine2(scriptId, undefined, undefined, offset);
sendMessage({
method: "Debugger.setBreakpoint",
params: {
location: {
scriptId: scriptId.toString(),
lineNumber: loc.line,
columnNumber: loc.column,
}
}
})
}
globalThis.receive = function(message) {
message = JSON.parse(message);
if (message.method == "Debugger.paused") {
addLocation(message.params.callFrames[0]);
stepInto();
}
}
const jsModule = await import('./index.mjs');
const box = jsModule.default.box;
enableDebugger();
setBreakpoint(box);
box();
disableDebugger();
print(JSON.stringify(locations))
""".trimIndent()
fun writeToFilesAndRunTest(mode: String, res: WasmCompilerResult) {
val sourceMap = res.parsedSourceMaps
val dir = File(outputDirBase, mode)
dir.mkdirs()
writeCompilationResult(res, dir, "index")
File(dir, "test.mjs").writeText(testFileContent)
val (jsFilePaths) = collectedJsArtifacts.saveJsArtifacts(dir)
val exception = try {
val result = WasmVM.V8.run(
entryMjs = "./${collectedJsArtifacts.entryPath}",
jsFiles = jsFilePaths,
workingDirectory = dir,
toolArgs = listOf("--enable-inspector", "--allow-natives-syntax")
)
val parsedLocations = FrameParser(result).parse().mapNotNull { frame ->
val originalFunctionName = frame.currentFunctionLocation.let {
sourceMap.segmentForGeneratedLocation(it.line, it.column)?.name ?: frame.functionName
}
val pausedLocation = sourceMap.segmentForGeneratedLocation(
frame.pausedLocation.line,
frame.pausedLocation.column,
)
val testFileName = pausedLocation?.sourceFileName
if (testFileName == null || pausedLocation.sourceLineNumber < 0) {
null
} else {
SteppingTestLoggedData(
pausedLocation.sourceLineNumber + 1,
false,
formatAsSteppingTestExpectation(
testFileName,
pausedLocation.sourceLineNumber + 1,
originalFunctionName,
false
)
)
}
}
checkSteppingTestResult(
frontendKind = mainModule.frontendKind,
mainModule.targetBackend ?: TargetBackend.WASM,
originalFile,
parsedLocations
)
null
} catch (e: Throwable) { e }
processExceptions(listOfNotNull(exception))
}
writeToFilesAndRunTest("dev", artifacts.compilerResult)
writeToFilesAndRunTest("dce", artifacts.compilerResultWithDCE)
}
private val WasmCompilerResult.parsedSourceMaps: SourceMap
get() = when (val parseResult = SourceMapParser.parse(sourceMap ?: error("Expect to have source maps for stepping test"))) {
is SourceMapSuccess -> parseResult.value
is SourceMapError -> error(parseResult.message)
}
private class Location(val line: Int, val column: Int)
private class Frame(val functionName: String, val currentFunctionLocation: Location, val pausedLocation: Location)
private class FrameParser(private val input: String) {
fun parse(): List<Frame> =
(parseJson(input) as JsonArray).elements
.map {
val frameObject = it as JsonObject
Frame(
frameObject.properties["functionName"].asString(),
Location(frameObject.properties["functionLine"].asInt(), frameObject.properties["functionColumn"].asInt()),
Location(frameObject.properties["line"].asInt(), frameObject.properties["column"].asInt())
)
}
private fun JsonNode?.asInt() = (this as JsonNumber).value.toInt()
private fun JsonNode?.asString() = (this as JsonString).value
}
}
@@ -17,50 +17,74 @@ internal sealed class WasmVM(val shortName: String) {
val name: String = javaClass.simpleName
protected val tool = ExternalTool(System.getProperty("javascript.engine.path.$name"))
abstract fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean = false)
abstract fun run(
entryMjs: String,
jsFiles: List<String>,
workingDirectory: File?,
disableExceptionHandlingIfPossible: Boolean = false,
toolArgs: List<String> = emptyList()
): String
object V8 : WasmVM("V8") {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) {
override fun run(
entryMjs: String,
jsFiles: List<String>,
workingDirectory: File?,
disableExceptionHandlingIfPossible: Boolean,
toolArgs: List<String>
) =
tool.run(
*toolArgs.toTypedArray(),
"--experimental-wasm-gc",
"--wasm-final-types",
"--wasm-disable-deprecated",
*jsFiles.toTypedArray(),
"--module",
entryMjs,
workingDirectory = workingDirectory
workingDirectory = workingDirectory,
)
}
}
object SpiderMonkey : WasmVM("SM") {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) {
override fun run(
entryMjs: String,
jsFiles: List<String>,
workingDirectory: File?,
disableExceptionHandlingIfPossible: Boolean,
toolArgs: List<String>
) =
tool.run(
*toolArgs.toTypedArray(),
"--wasm-verbose",
"--wasm-gc",
*if (disableExceptionHandlingIfPossible) arrayOf("--no-wasm-exceptions") else emptyArray(),
"--wasm-function-references",
*jsFiles.flatMap { listOf("-f", it) }.toTypedArray(),
"--module=$entryMjs",
workingDirectory = workingDirectory
workingDirectory = workingDirectory,
)
}
}
object NodeJs : WasmVM("NodeJs") {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) {
override fun run(
entryMjs: String,
jsFiles: List<String>,
workingDirectory: File?,
disableExceptionHandlingIfPossible: Boolean,
toolArgs: List<String>
) =
tool.run(
*toolArgs.toTypedArray(),
"--experimental-wasm-gc",
*jsFiles.flatMap { listOf("-f", it) }.toTypedArray(),
entryMjs,
workingDirectory = workingDirectory
)
}
}
}
internal class ExternalTool(val path: String) {
fun run(vararg arguments: String, workingDirectory: File? = null) {
fun run(vararg arguments: String, workingDirectory: File? = null): String {
val command = arrayOf(path, *arguments)
val processBuilder = ProcessBuilder(*command).redirectErrorStream(true)
@@ -70,7 +94,6 @@ internal class ExternalTool(val path: String) {
val process = processBuilder.start()
val commandString = command.joinToString(" ") { escapeShellArgument(it) }
if (toolLogsEnabled) {
println(
@@ -83,13 +106,21 @@ internal class ExternalTool(val path: String) {
}
// Print process output
val input = BufferedReader(InputStreamReader(process.inputStream))
while (true) println(input.readLine() ?: break)
val stdout = StringBuilder()
val bufferedStdout = BufferedReader(InputStreamReader(process.inputStream))
while (process.isAlive) {
val line = bufferedStdout.readLine() ?: break
stdout.appendLine(line)
println(line)
}
val exitValue = process.waitFor()
if (exitValue != 0) {
fail("Command \"$commandString\" terminated with exit code $exitValue")
}
return stdout.toString()
}
}