2021-05-07 02:32:58 -03:00
|
|
|
import json
|
2024-06-08 10:56:58 -03:00
|
|
|
import struct
|
|
|
|
|
import subprocess
|
2025-06-09 11:41:54 -03:00
|
|
|
import sys
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from pathlib import Path
|
2025-06-09 11:41:54 -03:00
|
|
|
from typing import Any
|
|
|
|
|
from typing import Literal
|
2025-12-03 22:20:37 -03:00
|
|
|
from typing import Optional
|
2025-06-09 11:41:54 -03:00
|
|
|
from typing import Union
|
2024-06-08 10:56:58 -03:00
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
TEST_MODULES_TO_SKIP: set[str] = set()
|
|
|
|
|
TESTS_TO_SKIP: set[str] = set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with open(Path(__file__).parent / "wasm_unimplemented_tests.txt", "r") as f:
|
|
|
|
|
all_skipped_tests = (x.strip() for x in f.readlines() if not x.startswith("#"))
|
|
|
|
|
for test in all_skipped_tests:
|
|
|
|
|
if test.startswith("module "):
|
|
|
|
|
TEST_MODULES_TO_SKIP.add(test[len("module ") :] + ".wasm")
|
|
|
|
|
elif test.startswith("test "):
|
|
|
|
|
TESTS_TO_SKIP.add(test[len("test ") :])
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
class ParseException(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateException(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2024-07-11 23:15:12 -03:00
|
|
|
class WasmPrimitiveValue:
|
|
|
|
|
kind: Literal["i32", "i64", "f32", "f64", "externref", "funcref"]
|
2026-04-29 22:13:47 -03:00
|
|
|
value: Optional[str]
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2024-07-11 23:15:12 -03:00
|
|
|
@dataclass
|
|
|
|
|
class WasmVector:
|
|
|
|
|
lanes: list[str]
|
|
|
|
|
num_bits: int
|
|
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
@dataclass
|
|
|
|
|
class WasmGCValue:
|
|
|
|
|
kind: str
|
|
|
|
|
value: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class EitherOf:
|
|
|
|
|
options: list["WasmValue"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WasmValue = Union[WasmPrimitiveValue, WasmVector, WasmGCValue, EitherOf]
|
2024-07-11 23:15:12 -03:00
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
@dataclass
|
|
|
|
|
class ModuleCommand:
|
|
|
|
|
line: int
|
|
|
|
|
file_name: Path
|
2026-04-29 19:09:46 -03:00
|
|
|
name: Optional[str]
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2026-06-10 08:29:55 -03:00
|
|
|
# An anonymous `(module definition ...)`.
|
|
|
|
|
@dataclass
|
|
|
|
|
class ModuleDefinitionCommand:
|
|
|
|
|
line: int
|
|
|
|
|
file_name: Path
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
@dataclass
|
|
|
|
|
class Invoke:
|
|
|
|
|
field: str
|
|
|
|
|
args: list[WasmValue]
|
2026-04-29 19:09:46 -03:00
|
|
|
module: Optional[str]
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Get:
|
|
|
|
|
field: str
|
2026-04-29 19:09:46 -03:00
|
|
|
module: Optional[str]
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
Action = Union[Invoke, Get]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Register:
|
|
|
|
|
line: int
|
2026-04-29 19:09:46 -03:00
|
|
|
name: Optional[str]
|
2024-06-08 10:56:58 -03:00
|
|
|
as_: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class AssertReturn:
|
|
|
|
|
line: int
|
|
|
|
|
action: Action
|
2026-04-29 19:09:46 -03:00
|
|
|
expected: Optional[WasmValue]
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class AssertTrap:
|
|
|
|
|
line: int
|
|
|
|
|
messsage: str
|
|
|
|
|
action: Action
|
|
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
@dataclass
|
|
|
|
|
class AssertException:
|
|
|
|
|
line: int
|
|
|
|
|
action: Action
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
@dataclass
|
|
|
|
|
class ActionCommand:
|
|
|
|
|
line: int
|
|
|
|
|
action: Action
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class AssertInvalid:
|
|
|
|
|
line: int
|
2025-12-03 22:20:37 -03:00
|
|
|
filename: Path
|
2024-06-08 10:56:58 -03:00
|
|
|
message: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Command = Union[
|
|
|
|
|
ModuleCommand,
|
2026-06-10 08:29:55 -03:00
|
|
|
ModuleDefinitionCommand,
|
2024-06-08 10:56:58 -03:00
|
|
|
AssertReturn,
|
|
|
|
|
AssertTrap,
|
|
|
|
|
ActionCommand,
|
|
|
|
|
AssertInvalid,
|
2025-12-03 22:20:37 -03:00
|
|
|
AssertException,
|
2024-06-08 10:56:58 -03:00
|
|
|
Register,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2024-07-11 16:33:49 -03:00
|
|
|
@dataclass
|
|
|
|
|
class ArithmeticNan:
|
|
|
|
|
num_bits: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class CanonicalNan:
|
|
|
|
|
num_bits: int
|
|
|
|
|
|
|
|
|
|
|
2024-07-11 23:15:12 -03:00
|
|
|
@dataclass
|
|
|
|
|
class GeneratedVector:
|
|
|
|
|
repr: str
|
|
|
|
|
num_bits: int
|
|
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
@dataclass
|
|
|
|
|
class GeneratedEitherOf:
|
|
|
|
|
options: list["GeneratedValue"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class GeneratedAnyFuncRef:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2026-06-10 08:29:55 -03:00
|
|
|
# `(ref.extern)` with no index: any non-null extern reference.
|
|
|
|
|
@dataclass
|
|
|
|
|
class GeneratedAnyExternRef:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2026-06-11 03:16:12 -03:00
|
|
|
# A gc reference expectation with no concrete index, e.g. `(ref.struct)`: any non-null
|
|
|
|
|
# reference. The harness cannot inspect the heap type, so only non-nullness is checked.
|
|
|
|
|
@dataclass
|
|
|
|
|
class GeneratedAnyGCRef:
|
|
|
|
|
kind: str
|
|
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
GeneratedValue = Union[
|
|
|
|
|
str,
|
|
|
|
|
ArithmeticNan,
|
|
|
|
|
CanonicalNan,
|
|
|
|
|
GeneratedVector,
|
|
|
|
|
GeneratedEitherOf,
|
|
|
|
|
GeneratedAnyFuncRef,
|
2026-06-10 08:29:55 -03:00
|
|
|
GeneratedAnyExternRef,
|
2026-06-11 03:16:12 -03:00
|
|
|
GeneratedAnyGCRef,
|
2025-12-03 22:20:37 -03:00
|
|
|
]
|
2024-07-11 16:33:49 -03:00
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
@dataclass
|
|
|
|
|
class WastDescription:
|
|
|
|
|
source_filename: str
|
|
|
|
|
commands: list[Command]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Context:
|
|
|
|
|
current_module_name: str
|
|
|
|
|
has_unclosed: bool
|
|
|
|
|
|
|
|
|
|
|
2026-04-29 22:13:47 -03:00
|
|
|
def parse_value(arg: dict[str, Any]) -> WasmValue:
|
2024-06-08 10:56:58 -03:00
|
|
|
type_ = arg["type"]
|
2026-04-29 21:28:31 -03:00
|
|
|
if type_ in ("i32", "i64", "f32", "f64"):
|
|
|
|
|
return WasmPrimitiveValue(type_, arg["value"])
|
|
|
|
|
if type_ in ("externref", "funcref"):
|
|
|
|
|
return WasmPrimitiveValue(type_, arg["value"] if "value" in arg else None)
|
|
|
|
|
if type_ == "refnull":
|
|
|
|
|
return WasmPrimitiveValue("externref", "null")
|
|
|
|
|
if type_ == "nullfuncref":
|
|
|
|
|
return WasmPrimitiveValue("funcref", "null")
|
|
|
|
|
if type_ == "v128":
|
|
|
|
|
if not isinstance(arg["value"], list):
|
|
|
|
|
raise ParseException("Got unknown type for Wasm value")
|
|
|
|
|
num_bits = int(arg["lane_type"][1:])
|
|
|
|
|
return WasmVector(arg["value"], num_bits)
|
|
|
|
|
if type_ in (
|
|
|
|
|
"arrayref",
|
|
|
|
|
"structref",
|
|
|
|
|
"eqref",
|
|
|
|
|
"anyref",
|
|
|
|
|
"i31ref",
|
|
|
|
|
"exnref",
|
|
|
|
|
"nullref",
|
|
|
|
|
"nullexnref",
|
|
|
|
|
"nullexternref",
|
|
|
|
|
):
|
|
|
|
|
return WasmGCValue(type_, arg.get("value"))
|
|
|
|
|
if type_ == "either":
|
|
|
|
|
return EitherOf([parse_value(opt) for opt in arg["values"]])
|
|
|
|
|
raise ParseException(f"Unknown value type: {type_}")
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(raw_args: list[dict[str, str]]) -> list[WasmValue]:
|
|
|
|
|
return [parse_value(arg) for arg in raw_args]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_action(action: dict[str, Any]) -> Action:
|
2026-04-29 21:28:31 -03:00
|
|
|
action_type = action["type"]
|
|
|
|
|
if action_type == "invoke":
|
|
|
|
|
return Invoke(action["field"], parse_args(action["args"]), action.get("module"))
|
|
|
|
|
if action_type == "get":
|
|
|
|
|
return Get(action["field"], action.get("module"))
|
|
|
|
|
raise ParseException(f"Action not implemented: {action_type}")
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
def module_binary_filename(raw_cmd: dict[str, str]) -> Path:
|
|
|
|
|
return Path(raw_cmd["filename"] if raw_cmd.get("module_type") != "text" else raw_cmd["binary_filename"])
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def parse(raw: dict[str, Any]) -> WastDescription:
|
|
|
|
|
commands: list[Command] = []
|
2025-12-03 22:20:37 -03:00
|
|
|
defined_modules: dict[str, Path] = {}
|
2024-06-08 10:56:58 -03:00
|
|
|
for raw_cmd in raw["commands"]:
|
|
|
|
|
line = raw_cmd["line"]
|
|
|
|
|
cmd: Command
|
2026-04-29 21:28:31 -03:00
|
|
|
cmd_type = raw_cmd["type"]
|
|
|
|
|
if cmd_type == "module":
|
|
|
|
|
cmd = ModuleCommand(line, module_binary_filename(raw_cmd), raw_cmd.get("name"))
|
|
|
|
|
elif cmd_type == "module_definition":
|
|
|
|
|
if "name" in raw_cmd:
|
|
|
|
|
defined_modules[raw_cmd["name"]] = module_binary_filename(raw_cmd)
|
|
|
|
|
continue
|
2026-06-10 08:29:55 -03:00
|
|
|
cmd = ModuleDefinitionCommand(line, module_binary_filename(raw_cmd))
|
2026-04-29 21:28:31 -03:00
|
|
|
elif cmd_type == "module_instance":
|
|
|
|
|
cmd = ModuleCommand(line, defined_modules[raw_cmd["module"]], raw_cmd.get("instance"))
|
|
|
|
|
elif cmd_type == "action":
|
|
|
|
|
cmd = ActionCommand(line, parse_action(raw_cmd["action"]))
|
|
|
|
|
elif cmd_type == "register":
|
|
|
|
|
cmd = Register(line, raw_cmd.get("name"), raw_cmd["as"])
|
|
|
|
|
elif cmd_type == "assert_return":
|
|
|
|
|
cmd = AssertReturn(
|
|
|
|
|
line,
|
|
|
|
|
parse_action(raw_cmd["action"]),
|
|
|
|
|
(parse_value(raw_cmd["expected"][0]) if len(raw_cmd["expected"]) == 1 else None),
|
|
|
|
|
)
|
|
|
|
|
elif cmd_type in ("assert_trap", "assert_exhaustion"):
|
|
|
|
|
cmd = AssertTrap(line, raw_cmd["text"], parse_action(raw_cmd["action"]))
|
|
|
|
|
elif cmd_type in ("assert_invalid", "assert_malformed", "assert_uninstantiable", "assert_unlinkable"):
|
|
|
|
|
if raw_cmd.get("module_type") == "text":
|
|
|
|
|
continue
|
|
|
|
|
cmd = AssertInvalid(line, module_binary_filename(raw_cmd), raw_cmd["text"])
|
|
|
|
|
elif cmd_type == "assert_exception":
|
|
|
|
|
cmd = AssertException(line, parse_action(raw_cmd["action"]))
|
|
|
|
|
else:
|
|
|
|
|
raise ParseException(f"Unknown command type: {cmd_type}")
|
2024-06-08 10:56:58 -03:00
|
|
|
commands.append(cmd)
|
|
|
|
|
|
|
|
|
|
return WastDescription(raw["source_filename"], commands)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def escape(s: str) -> str:
|
|
|
|
|
return s.replace('"', '\\"')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_description(input_path: Path, name: str, out_path: Path) -> WastDescription:
|
|
|
|
|
out_json_path = out_path / f"{name}.json"
|
|
|
|
|
result = subprocess.run(
|
2025-12-03 22:20:37 -03:00
|
|
|
[
|
|
|
|
|
"wasm-tools",
|
|
|
|
|
"json-from-wast",
|
|
|
|
|
input_path,
|
|
|
|
|
"-o",
|
|
|
|
|
out_json_path,
|
|
|
|
|
"--wasm-dir",
|
|
|
|
|
str(out_path),
|
|
|
|
|
],
|
2024-06-08 10:56:58 -03:00
|
|
|
)
|
|
|
|
|
result.check_returncode()
|
|
|
|
|
with open(out_json_path, "r") as f:
|
|
|
|
|
description = json.load(f)
|
|
|
|
|
return parse(description)
|
|
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
def to_vector_element(value: str, bits: int, addition: str) -> str:
|
|
|
|
|
if value.isdigit():
|
|
|
|
|
return value + addition
|
|
|
|
|
if value.startswith("-") and value[1:].isdigit():
|
|
|
|
|
unsigned_value = (1 << bits) + int(value)
|
|
|
|
|
return str(unsigned_value) + addition
|
|
|
|
|
return f'"{value}"'
|
|
|
|
|
|
|
|
|
|
|
2024-07-11 23:15:12 -03:00
|
|
|
def gen_vector(vec: WasmVector, *, array=False) -> str:
|
|
|
|
|
addition = "n" if vec.num_bits == 64 else ""
|
2025-12-03 22:20:37 -03:00
|
|
|
vals = ", ".join(to_vector_element(v, vec.num_bits, addition) for v in vec.lanes)
|
2024-07-11 23:15:12 -03:00
|
|
|
if not array:
|
|
|
|
|
type_ = "BigUint64Array" if vec.num_bits == 64 else f"Uint{vec.num_bits}Array"
|
|
|
|
|
return f"new {type_}([{vals}])"
|
|
|
|
|
return f"[{vals}]"
|
|
|
|
|
|
|
|
|
|
|
2024-07-11 16:33:49 -03:00
|
|
|
def gen_value_arg(value: WasmValue) -> str:
|
2025-12-03 22:20:37 -03:00
|
|
|
if isinstance(value, WasmGCValue):
|
2026-06-11 03:16:12 -03:00
|
|
|
if value.value is None or value.value == "null":
|
|
|
|
|
return "null"
|
|
|
|
|
# A host reference (e.g. `(ref.host 2)` passed as anyref); the harness passes host
|
|
|
|
|
# references by their address.
|
|
|
|
|
return value.value
|
2025-12-03 22:20:37 -03:00
|
|
|
|
2024-07-11 23:15:12 -03:00
|
|
|
if isinstance(value, WasmVector):
|
|
|
|
|
return gen_vector(value)
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
if isinstance(value, EitherOf):
|
|
|
|
|
raise AssertionError("EitherOf should not appear here")
|
|
|
|
|
|
2026-04-29 22:13:47 -03:00
|
|
|
if value.value is None:
|
|
|
|
|
raise GenerateException("Cannot generate an argument without a concrete value")
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def unsigned_to_signed(uint: int, bits: int) -> int:
|
|
|
|
|
max_value = 2**bits
|
|
|
|
|
if uint >= 2 ** (bits - 1):
|
|
|
|
|
signed_int = uint - max_value
|
2021-05-07 02:32:58 -03:00
|
|
|
else:
|
2024-06-08 10:56:58 -03:00
|
|
|
signed_int = uint
|
|
|
|
|
|
|
|
|
|
return signed_int
|
|
|
|
|
|
|
|
|
|
def int_to_float_bitcast(uint: int) -> float:
|
|
|
|
|
b = struct.pack("I", uint)
|
|
|
|
|
f = struct.unpack("f", b)[0]
|
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
|
def int_to_float64_bitcast(uint: int) -> float:
|
|
|
|
|
uint64 = uint & 0xFFFFFFFFFFFFFFFF
|
|
|
|
|
b = struct.pack("Q", uint64)
|
|
|
|
|
f = struct.unpack("d", b)[0]
|
|
|
|
|
return f
|
|
|
|
|
|
2024-07-11 16:33:49 -03:00
|
|
|
def float_to_str(bits: int, *, double=False) -> str:
|
2024-07-06 12:01:26 -03:00
|
|
|
f = int_to_float64_bitcast(bits) if double else int_to_float_bitcast(bits)
|
2024-06-08 10:56:58 -03:00
|
|
|
return str(f)
|
|
|
|
|
|
2026-04-29 22:13:47 -03:00
|
|
|
if value.value.startswith("nan"):
|
2024-07-11 16:33:49 -03:00
|
|
|
raise GenerateException("Should not get indeterminate nan value as an argument")
|
|
|
|
|
if value.value == "inf":
|
2024-06-08 10:56:58 -03:00
|
|
|
return "Infinity"
|
2024-07-11 16:33:49 -03:00
|
|
|
if value.value == "-inf":
|
2024-06-08 10:56:58 -03:00
|
|
|
return "-Infinity"
|
|
|
|
|
|
2026-04-29 21:28:31 -03:00
|
|
|
if value.kind == "i32":
|
|
|
|
|
return str(unsigned_to_signed(int(value.value), 32))
|
|
|
|
|
if value.kind == "i64":
|
|
|
|
|
return str(unsigned_to_signed(int(value.value), 64)) + "n"
|
|
|
|
|
if value.kind == "f32":
|
|
|
|
|
return str(int(value.value)) + f" /* {float_to_str(int(value.value))} */"
|
|
|
|
|
if value.kind == "f64":
|
|
|
|
|
return str(int(value.value)) + f"n /* {float_to_str(int(value.value), double=True)} */"
|
|
|
|
|
if value.kind in ("externref", "funcref", "v128"):
|
|
|
|
|
return value.value
|
|
|
|
|
raise GenerateException(f"Not implemented: {value.kind}")
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2024-07-11 16:33:49 -03:00
|
|
|
def gen_value_result(value: WasmValue) -> GeneratedValue:
|
2024-07-11 23:15:12 -03:00
|
|
|
if isinstance(value, WasmVector):
|
|
|
|
|
return GeneratedVector(gen_vector(value, array=True), value.num_bits)
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
if isinstance(value, EitherOf):
|
|
|
|
|
return GeneratedEitherOf([gen_value_result(option) for option in value.options])
|
|
|
|
|
|
2026-06-11 03:16:12 -03:00
|
|
|
if isinstance(value, WasmGCValue):
|
|
|
|
|
if value.value == "null":
|
|
|
|
|
return "null"
|
|
|
|
|
# A bottom-type reference (e.g. `(ref.null none)`) can only ever be null.
|
|
|
|
|
if value.kind in ("nullref", "nullexnref", "nullexternref"):
|
|
|
|
|
return "null"
|
|
|
|
|
if value.value is None:
|
|
|
|
|
return GeneratedAnyGCRef(value.kind)
|
|
|
|
|
# A host reference result, surfaced by its address (e.g. `(ref.host 1)` as anyref).
|
|
|
|
|
return value.value
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
if value.kind == "funcref" and value.value is None:
|
|
|
|
|
return GeneratedAnyFuncRef()
|
|
|
|
|
|
2026-06-10 08:29:55 -03:00
|
|
|
if value.kind == "externref" and value.value is None:
|
|
|
|
|
return GeneratedAnyExternRef()
|
|
|
|
|
|
2026-04-29 22:13:47 -03:00
|
|
|
if value.kind == "f32" or value.kind == "f64":
|
|
|
|
|
assert value.value is not None
|
|
|
|
|
if value.value.startswith("nan"):
|
|
|
|
|
num_bits = int(value.kind[1:])
|
|
|
|
|
if value.value == "nan:canonical":
|
|
|
|
|
return CanonicalNan(num_bits)
|
|
|
|
|
if value.value == "nan:arithmetic":
|
|
|
|
|
return ArithmeticNan(num_bits)
|
|
|
|
|
raise GenerateException(f"Unknown indeterminate nan: {value.value}")
|
2024-07-11 16:33:49 -03:00
|
|
|
return gen_value_arg(value)
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_args(args: list[WasmValue]) -> str:
|
2024-07-11 16:33:49 -03:00
|
|
|
return ",".join(gen_value_arg(arg) for arg in args)
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
def gen_test_command_for_module(file_name):
|
|
|
|
|
if str(file_name) in TEST_MODULES_TO_SKIP:
|
|
|
|
|
return "_test.skip"
|
|
|
|
|
return "_test"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gen_test_command_for_invoke(module_name):
|
|
|
|
|
if module_name in TESTS_TO_SKIP:
|
|
|
|
|
return "_test.skip"
|
|
|
|
|
return "_test"
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_module_command(command: ModuleCommand, ctx: Context):
|
|
|
|
|
if ctx.has_unclosed:
|
|
|
|
|
print("});")
|
|
|
|
|
print(
|
|
|
|
|
f"""describe("{command.file_name.stem}", () => {{
|
|
|
|
|
let _test = test;
|
|
|
|
|
let content, module;
|
|
|
|
|
try {{
|
|
|
|
|
content = readBinaryWasmFile("Fixtures/SpecTests/{command.file_name}");
|
|
|
|
|
module = parseWebAssemblyModule(content, globalImportObject);
|
|
|
|
|
}} catch (e) {{
|
2025-12-03 22:20:37 -03:00
|
|
|
{gen_test_command_for_module(command.file_name)}("parse (line {command.line})", () => expect().fail(e));
|
2024-06-08 10:56:58 -03:00
|
|
|
_test = test.skip;
|
|
|
|
|
_test.skip = test.skip;
|
|
|
|
|
}}
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
if command.name is not None:
|
|
|
|
|
print(f'namedModules["{command.name}"] = module;')
|
|
|
|
|
ctx.current_module_name = command.file_name.stem
|
|
|
|
|
ctx.has_unclosed = True
|
|
|
|
|
|
|
|
|
|
|
2026-06-10 08:29:55 -03:00
|
|
|
def gen_module_definition_command(command: ModuleDefinitionCommand, ctx: Context):
|
|
|
|
|
if ctx.has_unclosed:
|
|
|
|
|
print("});")
|
|
|
|
|
ctx.has_unclosed = False
|
|
|
|
|
stem = command.file_name.stem
|
|
|
|
|
print(
|
|
|
|
|
f"""
|
|
|
|
|
describe("{stem}", () => {{
|
|
|
|
|
let _test = test;
|
|
|
|
|
{gen_test_command_for_module(command.file_name)}("validate (line {command.line})", () => {{
|
|
|
|
|
content = readBinaryWasmFile("Fixtures/SpecTests/{command.file_name}");
|
|
|
|
|
validateWebAssemblyModule(content);
|
|
|
|
|
}});
|
|
|
|
|
}});"""
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_invalid(invalid: AssertInvalid, ctx: Context):
|
2024-07-11 13:45:01 -03:00
|
|
|
# TODO: Remove this once the multiple memories proposal is standardized.
|
|
|
|
|
# We support the multiple memories proposal, so spec-tests that check that
|
|
|
|
|
# we don't do not make any sense to include right now.
|
|
|
|
|
if invalid.message == "multiple memories":
|
|
|
|
|
return
|
2024-06-08 10:56:58 -03:00
|
|
|
if ctx.has_unclosed:
|
|
|
|
|
print("});")
|
|
|
|
|
ctx.has_unclosed = False
|
2025-12-03 22:20:37 -03:00
|
|
|
stem = invalid.filename.stem
|
2024-06-08 10:56:58 -03:00
|
|
|
print(
|
|
|
|
|
f"""
|
|
|
|
|
describe("{stem}", () => {{
|
|
|
|
|
let _test = test;
|
2025-12-03 22:20:37 -03:00
|
|
|
{gen_test_command_for_module(invalid.filename)}("parse of {stem} (line {invalid.line})", () => {{
|
2024-06-08 10:56:58 -03:00
|
|
|
content = readBinaryWasmFile("Fixtures/SpecTests/{invalid.filename}");
|
|
|
|
|
expect(() => parseWebAssemblyModule(content, globalImportObject)).toThrow(Error, "{invalid.message}");
|
|
|
|
|
}});
|
|
|
|
|
}});"""
|
2021-05-07 02:32:58 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-07-11 23:15:12 -03:00
|
|
|
def gen_pretty_expect(expr: str, got: str, expect: str):
|
2026-06-11 03:16:12 -03:00
|
|
|
print(f"if (!({expr})) {{ expect().fail(`Failed with ${{{got}}}, expected {expect}`); }}")
|
2024-07-11 23:15:12 -03:00
|
|
|
|
|
|
|
|
|
2025-12-03 22:20:37 -03:00
|
|
|
def gen_expectation(gen_result: GeneratedValue, module: str):
|
2026-04-29 21:28:31 -03:00
|
|
|
if isinstance(gen_result, str):
|
|
|
|
|
print(f"expect(_result).toBe({gen_result});")
|
|
|
|
|
return
|
|
|
|
|
if isinstance(gen_result, GeneratedAnyFuncRef):
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
f"isValidFuncrefIn(_result, {module})",
|
|
|
|
|
"_result",
|
|
|
|
|
"(ref.func)",
|
|
|
|
|
)
|
|
|
|
|
return
|
2026-06-10 08:29:55 -03:00
|
|
|
if isinstance(gen_result, GeneratedAnyExternRef):
|
|
|
|
|
# Null extern references surface as JS null; live ones as their address.
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
"_result !== null",
|
|
|
|
|
"_result",
|
|
|
|
|
"(ref.extern)",
|
|
|
|
|
)
|
|
|
|
|
return
|
2026-06-11 03:16:12 -03:00
|
|
|
if isinstance(gen_result, GeneratedAnyGCRef):
|
|
|
|
|
# Null gc references surface as JS null; the harness cannot check the heap type of a
|
|
|
|
|
# live one, only that it is not null.
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
"_result !== null",
|
|
|
|
|
"_result",
|
|
|
|
|
f"(ref.{gen_result.kind})",
|
|
|
|
|
)
|
|
|
|
|
return
|
2026-04-29 21:28:31 -03:00
|
|
|
if isinstance(gen_result, ArithmeticNan):
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
f"isArithmeticNaN{gen_result.num_bits}(_result)",
|
|
|
|
|
"_result",
|
|
|
|
|
"nan:arithmetic",
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
if isinstance(gen_result, CanonicalNan):
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
f"isCanonicalNaN{gen_result.num_bits}(_result)",
|
|
|
|
|
"_result",
|
|
|
|
|
"nan:canonical",
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
if isinstance(gen_result, GeneratedVector):
|
|
|
|
|
if gen_result.num_bits == 64:
|
|
|
|
|
array = "new BigUint64Array(_result)"
|
|
|
|
|
else:
|
|
|
|
|
array = f"new Uint{gen_result.num_bits}Array(_result)"
|
|
|
|
|
print(f"/* {gen_result} */ ", end="")
|
|
|
|
|
gen_pretty_expect(
|
|
|
|
|
f"testSIMDVector({gen_result.repr}, {array})",
|
|
|
|
|
array,
|
|
|
|
|
gen_result.repr,
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
assert isinstance(gen_result, GeneratedEitherOf)
|
|
|
|
|
print("let matched = false;")
|
|
|
|
|
print("let error_sample = null;")
|
|
|
|
|
expectations = []
|
|
|
|
|
for option in gen_result.options:
|
|
|
|
|
print("try {")
|
|
|
|
|
gen_expectation(option, module)
|
|
|
|
|
print("matched = true;")
|
|
|
|
|
print("} catch (e) { error_sample = e; }")
|
|
|
|
|
expectation = "unknown"
|
|
|
|
|
if isinstance(option, str):
|
|
|
|
|
expectation = option
|
|
|
|
|
elif isinstance(option, ArithmeticNan):
|
|
|
|
|
expectation = "nan:arithmetic"
|
|
|
|
|
elif isinstance(option, CanonicalNan):
|
|
|
|
|
expectation = "nan:canonical"
|
|
|
|
|
elif isinstance(option, GeneratedVector):
|
|
|
|
|
expectation = option.repr
|
|
|
|
|
expectations.append(expectation)
|
|
|
|
|
print(
|
|
|
|
|
f"if (!matched) {{ expect().fail(`Expected one of {', '.join(expectations)}, got ${{_result}}: ${{error_sample}}`); }}"
|
|
|
|
|
)
|
2025-12-03 22:20:37 -03:00
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_invoke(
|
|
|
|
|
line: int,
|
|
|
|
|
invoke: Invoke,
|
2026-04-29 19:09:46 -03:00
|
|
|
result: Optional[WasmValue],
|
2024-06-08 10:56:58 -03:00
|
|
|
ctx: Context,
|
|
|
|
|
*,
|
2026-04-29 19:09:46 -03:00
|
|
|
fail_msg: Optional[str] = None,
|
2024-06-08 10:56:58 -03:00
|
|
|
):
|
2024-07-11 13:31:28 -03:00
|
|
|
if not ctx.has_unclosed:
|
|
|
|
|
print(f'describe("inline (line {line}))", () => {{\nlet _test = test;\n')
|
2024-06-08 10:56:58 -03:00
|
|
|
module = "module"
|
|
|
|
|
if invoke.module is not None:
|
|
|
|
|
module = f'namedModules["{invoke.module}"]'
|
2025-05-22 08:30:45 -03:00
|
|
|
utf8 = str(invoke.field.encode("utf8"))[2:-1].replace("\\'", "'").replace("`", "${'`'}")
|
2024-06-08 10:56:58 -03:00
|
|
|
print(
|
2025-12-03 22:20:37 -03:00
|
|
|
f"""{gen_test_command_for_invoke(ctx.current_module_name)}(`execution of {ctx.current_module_name}: {utf8} (line {line})`, () => {{
|
2024-07-11 13:31:28 -03:00
|
|
|
let _field = {module}.getExport(decodeURIComponent(escape(`{utf8}`)));
|
2024-06-08 10:56:58 -03:00
|
|
|
expect(_field).not.toBeUndefined();"""
|
2021-05-07 02:32:58 -03:00
|
|
|
)
|
2024-06-08 10:56:58 -03:00
|
|
|
if fail_msg is not None:
|
|
|
|
|
print(f'expect(() => {module}.invoke(_field)).toThrow(Error, "{fail_msg}");')
|
|
|
|
|
else:
|
|
|
|
|
print(f"let _result = {module}.invoke(_field, {gen_args(invoke.args)});")
|
|
|
|
|
if result is not None:
|
2024-07-11 16:33:49 -03:00
|
|
|
gen_result = gen_value_result(result)
|
2025-12-03 22:20:37 -03:00
|
|
|
gen_expectation(gen_result, module)
|
2024-06-08 10:56:58 -03:00
|
|
|
print("});")
|
2024-07-11 13:31:28 -03:00
|
|
|
if not ctx.has_unclosed:
|
|
|
|
|
print("});")
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
2026-04-29 19:09:46 -03:00
|
|
|
def gen_get(line: int, get: Get, result: Optional[WasmValue], ctx: Context):
|
2024-06-08 10:56:58 -03:00
|
|
|
module = "module"
|
|
|
|
|
if get.module is not None:
|
|
|
|
|
module = f'namedModules["{get.module}"]'
|
|
|
|
|
print(
|
2025-12-03 22:20:37 -03:00
|
|
|
f"""{gen_test_command_for_invoke(ctx.current_module_name)}("execution of {ctx.current_module_name}: get-{get.field} (line {line})", () => {{
|
2024-06-08 10:56:58 -03:00
|
|
|
let _field = {module}.getExport("{get.field}");"""
|
|
|
|
|
)
|
|
|
|
|
if result is not None:
|
2024-07-11 16:33:49 -03:00
|
|
|
print(f"expect(_field).toBe({gen_value_result(result)});")
|
2024-06-08 10:56:58 -03:00
|
|
|
print("});")
|
2021-05-07 02:32:58 -03:00
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_register(register: Register, _: Context):
|
|
|
|
|
module = "module"
|
|
|
|
|
if register.name is not None:
|
2024-06-10 19:42:05 -03:00
|
|
|
module = f'namedModules["{register.name}"]'
|
2024-06-08 10:56:58 -03:00
|
|
|
print(f'globalImportObject["{register.as_}"] = {module};')
|
2021-06-21 12:40:41 -03:00
|
|
|
|
|
|
|
|
|
2024-06-08 10:56:58 -03:00
|
|
|
def gen_command(command: Command, ctx: Context):
|
2026-04-29 21:28:31 -03:00
|
|
|
if isinstance(command, ModuleCommand):
|
|
|
|
|
gen_module_command(command, ctx)
|
|
|
|
|
return
|
2026-06-10 08:29:55 -03:00
|
|
|
if isinstance(command, ModuleDefinitionCommand):
|
|
|
|
|
gen_module_definition_command(command, ctx)
|
|
|
|
|
return
|
2026-04-29 21:28:31 -03:00
|
|
|
if isinstance(command, ActionCommand):
|
|
|
|
|
if isinstance(command.action, Invoke):
|
|
|
|
|
gen_invoke(command.line, command.action, None, ctx)
|
|
|
|
|
else:
|
|
|
|
|
raise GenerateException(f"Not implemented: top-level {type(command.action)}")
|
|
|
|
|
return
|
|
|
|
|
if isinstance(command, AssertInvalid):
|
|
|
|
|
gen_invalid(command, ctx)
|
|
|
|
|
return
|
|
|
|
|
if isinstance(command, Register):
|
|
|
|
|
gen_register(command, ctx)
|
|
|
|
|
return
|
|
|
|
|
if isinstance(command, AssertReturn):
|
|
|
|
|
if isinstance(command.action, Invoke):
|
|
|
|
|
gen_invoke(command.line, command.action, command.expected, ctx)
|
|
|
|
|
else:
|
|
|
|
|
gen_get(command.line, command.action, command.expected, ctx)
|
|
|
|
|
return
|
|
|
|
|
if isinstance(command, AssertTrap):
|
|
|
|
|
if not isinstance(command.action, Invoke):
|
|
|
|
|
raise GenerateException(f"Not implemented: {type(command.action)}")
|
|
|
|
|
gen_invoke(command.line, command.action, None, ctx, fail_msg=command.messsage)
|
|
|
|
|
return
|
|
|
|
|
assert isinstance(command, AssertException)
|
|
|
|
|
if not isinstance(command.action, Invoke):
|
|
|
|
|
raise GenerateException(f"Not implemented: {type(command.action)}")
|
|
|
|
|
gen_invoke(command.line, command.action, None, ctx, fail_msg="exception")
|
2024-06-08 10:56:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate(description: WastDescription):
|
|
|
|
|
print("let globalImportObject = {};\nlet namedModules = {};\n")
|
|
|
|
|
ctx = Context("", False)
|
|
|
|
|
for command in description.commands:
|
|
|
|
|
gen_command(command, ctx)
|
|
|
|
|
if ctx.has_unclosed:
|
|
|
|
|
print("});")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clean_up(path: Path):
|
|
|
|
|
for file in path.iterdir():
|
|
|
|
|
if file.suffix in ("wat", "json"):
|
|
|
|
|
file.unlink()
|
2021-08-30 10:01:06 -03:00
|
|
|
|
|
|
|
|
|
2021-05-07 02:32:58 -03:00
|
|
|
def main():
|
2024-06-08 10:56:58 -03:00
|
|
|
input_path = Path(sys.argv[1])
|
|
|
|
|
name = sys.argv[2]
|
|
|
|
|
out_path = Path(sys.argv[3])
|
|
|
|
|
|
|
|
|
|
description = make_description(input_path, name, out_path)
|
|
|
|
|
generate(description)
|
|
|
|
|
clean_up(out_path)
|
2021-05-07 02:32:58 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|