///|
test "wast_to_module parse errors include filename line and column" {
match wast_to_module("(module\n (func", filename="sample.wast") {
Ok(_) => fail("expected parse error for incomplete module text")
Err(msg) => {
assert_true(msg.contains("sample.wast"))
assert_true(msg.contains(":2:8"))
}
}
}
///|
test "script_to_wast escapes register names and can be parsed back" {
let script : WastScript = { commands: [Register("line\nbreak", None)], }
let text = match script_to_wast(script) {
Ok(t) => t
Err(e) => fail("unexpected script_to_wast error: \{e}")
}
assert_true(text.contains("\\n"))
assert_true(text.contains("(register"))
let parsed = match wast_to_script("(module)") {
Ok(s) => s
Err(e) => fail("unexpected wast_to_script error: \{e}")
}
@debug.assert_eq(parsed.commands.length(), 1)
}
///|
test "module_to_wast preserves descriptor instruction keywords" {
let src =
#|(module
#| (func (param funcref) (result i32)
#| (drop (ref.get_desc 0 (local.get 0)))
#| (drop (ref.test_desc 0 (local.get 0)))
#| (drop (ref.test_desc_null 0 (local.get 0)))
#| (drop (ref.cast_desc_eq 0 (local.get 0)))
#| (drop (ref.cast_desc_eq_null 0 (local.get 0)))
#| (i32.const 0)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) => fail("unexpected parse error: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) => fail("unexpected print error: \{e}")
}
assert_true(text.contains("ref.get_desc"))
assert_true(text.contains("ref.test_desc 0"))
assert_true(text.contains("ref.test_desc_null 0"))
assert_true(text.contains("ref.cast_desc_eq 0"))
assert_true(text.contains("ref.cast_desc_eq_null 0"))
}
///|
test "module_to_wast preserves struct allocation instruction keywords" {
let src =
#|(module
#| (func
#| (drop (struct.new 0))
#| (drop (struct.new_default 0))
#| (drop (struct.new_desc 0 (ref.null none)))
#| (drop (struct.new_default_desc 0 (ref.null none)))))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) => fail("unexpected parse error: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) => fail("unexpected print error: \{e}")
}
assert_true(text.contains("struct.new 0"))
assert_true(text.contains("struct.new_default 0"))
assert_true(text.contains("struct.new_desc 0"))
assert_true(text.contains("struct.new_default_desc 0"))
}
///|
test "module_to_wast preserves function annotations" {
let src =
#|(module
#| (@binaryen.js.called)
#| (import "env" "imp" (func $imp))
#| (@binaryen.idempotent)
#| (@metadata.code.inline "\00")
#| (func $f))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail("unexpected parse error for function annotation fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail("unexpected print error for function annotation fixture: \{e}")
}
assert_true(text.contains("(@binaryen.js.called)"))
assert_true(text.contains("(@binaryen.idempotent)"))
assert_true(text.contains("(@metadata.code.inline \"\\00\")"))
match wast_to_module(text, filename="printed-function-annotations.wat") {
Ok(parsed) =>
match parsed.fields {
[ImportField(im), FuncField(func)] => {
@debug.assert_eq(im.annotations, [
{ name: "binaryen.js.called", args: [], },
])
@debug.assert_eq(func.annotations, [
{ name: "binaryen.idempotent", args: [], },
{ name: "metadata.code.inline", args: ["\"\\00\""], },
])
}
_ =>
fail("expected import + function fields after annotation roundtrip")
}
Err(e) => fail("expected function annotation output to parse back: \{e}")
}
}
///|
test "module_to_wast preserves table elem-abbreviation offsets and typed items" {
let src =
#|(module
#| (func $f)
#| (table 4 funcref (elem (i32.const 2) funcref (item (ref.null func)) (item (ref.func $f)))))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail("unexpected parse error for typed table elem-abbrev fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail("unexpected print error for typed table elem-abbrev fixture: \{e}")
}
assert_true(text.contains("(i32.const 2)"))
assert_true(text.contains("(item"))
assert_true(text.contains("ref.null func"))
assert_true(text.contains("ref.func $f"))
match wast_to_module(text, filename="printed-table-typed-items.wat") {
Ok(_) => ()
Err(e) =>
fail(
"expected module_to_wast typed table elem-abbrev output to parse back: \{e}",
)
}
}
///|
test "module_to_wast preserves concrete typed-heap value types" {
let src =
#|(module
#| (type $t (func))
#| (func (param (ref $t)) (result (ref null $t))
#| (ref.null $t)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail("unexpected parse error for typed-heap value-type fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail("unexpected print error for typed-heap value-type fixture: \{e}")
}
assert_true(text.contains("(param (ref $t))"))
assert_true(text.contains("(result (ref null $t))"))
assert_true(text.contains("ref.null $t"))
match wast_to_module(text, filename="printed-typed-heap-values.wat") {
Ok(_) => ()
Err(e) =>
fail(
"expected module_to_wast typed-heap value-type output to parse back: \{e}",
)
}
}
///|
test "module_to_wast preserves exact typed-heap value types" {
let src =
#|(module
#| (type $t (func))
#| (func (param (ref (exact $t))) (result (ref (exact $t)))
#| (local.get 0)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail(
"unexpected parse error for exact typed-heap value-type fixture: \{e}",
)
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail(
"unexpected print error for exact typed-heap value-type fixture: \{e}",
)
}
assert_true(text.contains("(param (ref (exact $t)))"))
assert_true(text.contains("(result (ref (exact $t)))"))
assert_true(text.contains("local.get 0"))
match wast_to_module(text, filename="printed-exact-typed-heap-values.wat") {
Ok(_) => ()
Err(e) =>
fail(
"expected module_to_wast exact typed-heap value-type output to parse back: \{e}",
)
}
}
///|
test "module_to_wast preserves abstract non-func heap refs in value types and ref.null" {
let src =
#|(module
#| (func
#| (param (ref any) (ref eq) (ref i31) (ref none) (ref nofunc) (ref noextern) (ref noexn))
#| (result (ref null any) (ref null eq) (ref null i31) (ref null none) (ref null nofunc) (ref null noextern) (ref null noexn))
#| (ref.null any)
#| (ref.null eq)
#| (ref.null i31)
#| (ref.null none)
#| (ref.null nofunc)
#| (ref.null noextern)
#| (ref.null noexn)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail(
"unexpected parse error for abstract heap-kind value-type fixture: \{e}",
)
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail(
"unexpected print error for abstract heap-kind value-type fixture: \{e}",
)
}
assert_true(text.contains("(ref any)"))
assert_true(text.contains("(ref eq)"))
assert_true(text.contains("(ref i31)"))
assert_true(text.contains("(ref none)"))
assert_true(text.contains("(ref nofunc)"))
assert_true(text.contains("(ref noextern)"))
assert_true(text.contains("(ref noexn)"))
assert_true(text.contains("(ref null any)"))
assert_true(text.contains("(ref null eq)"))
assert_true(text.contains("(ref null i31)"))
assert_true(text.contains("(ref null none)"))
assert_true(text.contains("(ref null nofunc)"))
assert_true(text.contains("(ref null noextern)"))
assert_true(text.contains("(ref null noexn)"))
assert_true(text.contains("ref.null any"))
assert_true(text.contains("ref.null eq"))
assert_true(text.contains("ref.null i31"))
assert_true(text.contains("ref.null none"))
assert_true(text.contains("ref.null nofunc"))
assert_true(text.contains("ref.null noextern"))
assert_true(text.contains("ref.null noexn"))
match wast_to_module(text, filename="printed-abstract-heap-values.wat") {
Ok(_) => ()
Err(e) =>
fail(
"expected module_to_wast abstract heap-kind output to parse back: \{e}",
)
}
}
///|
test "module_to_wast preserves abstract heap refs across table global tag and import surfaces" {
let src =
#|(module
#| (type $tag_t (func (param (ref null noextern))))
#| (import "env" "itbl" (table 1 (ref null nofunc)))
#| (import "env" "ig" (global (mut (ref null any))))
#| (import "env" "itag" (tag (type $tag_t)))
#| (table 2 (ref null noextern))
#| (global (mut (ref null none)) (ref.null none))
#| (tag (type $tag_t)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) =>
fail(
"unexpected parse error for abstract heap-kind module-surface fixture: \{e}",
)
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) =>
fail(
"unexpected print error for abstract heap-kind module-surface fixture: \{e}",
)
}
assert_true(text.contains("(table 1 (ref null nofunc))"))
assert_true(text.contains("(global (mut (ref null any)))"))
assert_true(text.contains("(tag (type $tag_t))"))
assert_true(text.contains("(table 2 (ref null noextern))"))
assert_true(text.contains("(global (mut (ref null none))"))
assert_true(text.contains("ref.null none"))
assert_true(text.contains("(param (ref null noextern))"))
match
wast_to_module(text, filename="printed-abstract-heap-module-surfaces.wat") {
Ok(_) => ()
Err(e) =>
fail(
"expected module_to_wast abstract heap-kind module-surface output to parse back: \{e}",
)
}
}
///|
test "module_to_wast preserves struct type defs with subtype metadata" {
let src =
#|(module
#| (type $base (struct))
#| (type $node
#| (sub final $base
#| (descriptor $node_desc)
#| (struct
#| (field i32)
#| (field (mut (ref null $base))))))
#| (type $node_desc
#| (describes $node)
#| (struct (field (ref $node)))))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) => fail("unexpected parse error for struct type-def fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) => fail("unexpected print error for struct type-def fixture: \{e}")
}
assert_true(text.contains("(type $base (struct))"))
assert_true(text.contains("(sub final $base"))
assert_true(text.contains("(descriptor $node_desc)"))
assert_true(text.contains("(field i32)"))
assert_true(text.contains("(field (mut (ref null $base)))"))
assert_true(text.contains("(describes $node)"))
assert_true(text.contains("(field (ref $node))"))
match wast_to_module(text, filename="printed-struct-type-defs.wat") {
Ok(_) => ()
Err(e) =>
fail("expected module_to_wast struct type-def output to parse back: \{e}")
}
}
///|
test "module_to_wast preserves array type defs with subtype metadata" {
let src =
#|(module
#| (type $bytes (array i8))
#| (type $arr
#| (sub $bytes
#| (descriptor $arr_desc)
#| (array (mut (ref null $bytes)))))
#| (type $arr_desc
#| (describes $arr)
#| (array i16)))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) => fail("unexpected parse error for array type-def fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) => fail("unexpected print error for array type-def fixture: \{e}")
}
assert_true(text.contains("(type $bytes (array i8))"))
assert_true(text.contains("(sub $bytes"))
assert_true(text.contains("(descriptor $arr_desc)"))
assert_true(text.contains("(array (mut (ref null $bytes)))"))
assert_true(text.contains("(describes $arr)"))
assert_true(text.contains("(array i16)"))
match wast_to_module(text, filename="printed-array-type-defs.wat") {
Ok(_) => ()
Err(e) =>
fail("expected module_to_wast array type-def output to parse back: \{e}")
}
}
///|
test "module_to_wast preserves rec groups with gc type defs" {
let src =
#|(module
#| (rec
#| (type $node
#| (descriptor $node_desc)
#| (struct (field (ref null $node))))
#| (type $node_desc
#| (describes $node)
#| (struct)))
#| (rec
#| (type $sig (func (param i32) (result i32)))))
let mod = match wast_to_module(src) {
Ok(m) => m
Err(e) => fail("unexpected parse error for rec-group fixture: \{e}")
}
let text = match module_to_wast(mod) {
Ok(t) => t
Err(e) => fail("unexpected print error for rec-group fixture: \{e}")
}
assert_true(text.contains("(rec"))
assert_true(text.contains("(type $node"))
assert_true(text.contains("(type $sig (func (param i32) (result i32)))"))
match wast_to_module(text, filename="printed-rec-groups.wat") {
Ok(roundtrip) =>
match roundtrip.fields {
[RecField([_, _]), RecField([_])] => ()
_ => fail("expected roundtripped rec groups to stay grouped")
}
Err(e) =>
fail("expected module_to_wast rec-group output to parse back: \{e}")
}
}
///|
test "wast_to_module malformed fixtures return targeted parse diagnostics" {
let fixtures : Array[(String, String, String, String)] = [
(
"invalid i32 constant", "(module (func (result i32) i32.const banana))", "expected integer literal",
"i32-const-fixture.wast",
),
(
"invalid i64 constant", "(module (func (result i64) i64.const banana))", "expected integer literal",
"i64-const-fixture.wast",
),
(
"invalid f32 constant", "(module (func (result f32) f32.const banana))", "expected numeric literal",
"f32-const-fixture.wast",
),
(
"invalid f64 constant", "(module (func (result f64) f64.const banana))", "expected numeric literal",
"f64-const-fixture.wast",
),
(
"unknown module field", "(module (oops))", "expected module field", "field-fixture.wast",
),
(
"import missing descriptor", "(module (import \"env\" \"f\" ()))", "expected import descriptor",
"import-fixture.wast",
),
(
"memory missing minimum limit", "(module (memory))", "expected minimum limit",
"memory-fixture.wast",
),
(
"mutable global missing value type", "(module (global (mut) (i32.const 0)))",
"expected value type", "global-fixture.wast",
),
(
"incomplete module expression", "(module (func", "expected ')'", "eof-fixture.wast",
),
]
for fixture in fixtures {
let (name, source, detail, filename) = fixture
match wast_to_module(source, filename~) {
Ok(_) => fail("expected malformed module fixture '\{name}' to fail")
Err(msg) => {
assert_true(msg.contains(detail))
assert_true(msg.contains(filename))
assert_true(msg.contains(":1"))
}
}
}
}
///|
test "wast_to_module rejects descriptor clauses before describes clauses" {
let source = "(module (rec (type $a (descriptor $b) (struct)) (type $b (descriptor $c) (describes $a) (struct)) (type $c (describes $b) (struct))))"
match wast_to_module(source, filename="descriptor-order-fixture.wast") {
Ok(_) => fail("expected descriptor metadata order fixture to fail")
Err(msg) =>
assert_true(
msg.contains("describes clause must precede descriptor clause"),
)
}
}