// |jit-test| skip-if: !wasmGcEnabled() // We'll be running some binary-format tests shortly. load(libdir + "wasm-binary.js"); const v2vSigSection = sigSection([{args:[], ret:VoidCode}]); function checkInvalid(body, errorMessage) { assertErrorMessage(() => new WebAssembly.Module( moduleWithSections([v2vSigSection, declSection([0]), bodySection([body])])), WebAssembly.CompileError, errorMessage); } // General test case for struct.new, struct.get, and struct.set: binary tree // manipulation. { let bin = wasmTextToBinary( `(module (import "" "print_lp" (func $print_lp)) (import "" "print_rp" (func $print_rp)) (import "" "print_int" (func $print_int (param i32))) (type $wabbit (struct (field $x (mut i32)) (field $left (mut (ref null $wabbit))) (field $right (mut (ref null $wabbit))))) (global $g (mut (ref null $wabbit)) (ref.null $wabbit)) (global $k (mut i32) (i32.const 0)) (func (export "init") (param $n i32) (global.set $g (call $make (local.get $n)))) (func $make (param $n i32) (result (ref null $wabbit)) (local $tmp i32) (local.set $tmp (global.get $k)) (global.set $k (i32.add (local.get $tmp) (i32.const 1))) (if (result (ref null $wabbit)) (i32.le_s (local.get $n) (i32.const 2)) (struct.new $wabbit (local.get $tmp) (ref.null $wabbit) (ref.null $wabbit)) (block (result (ref null $wabbit)) (struct.new $wabbit (local.get $tmp) (call $make (i32.sub (local.get $n) (i32.const 1))) (call $make (i32.sub (local.get $n) (i32.const 2))))))) (func (export "accumulate") (result i32) (call $accum (global.get $g))) (func $accum (param $w (ref null $wabbit)) (result i32) (if (result i32) (ref.is_null (local.get $w)) (i32.const 0) (i32.add (struct.get $wabbit 0 (local.get $w)) (i32.sub (call $accum (struct.get $wabbit 1 (local.get $w))) (call $accum (struct.get $wabbit 2 (local.get $w))))))) (func (export "reverse") (call $reverse (global.get $g))) (func $reverse (param $w (ref null $wabbit)) (local $tmp (ref null $wabbit)) (if (i32.eqz (ref.is_null (local.get $w))) (block (struct.set $wabbit 0 (local.get $w) (i32.mul (i32.const 2) (struct.get $wabbit 0 (local.get $w)))) (local.set $tmp (struct.get $wabbit 1 (local.get $w))) (struct.set $wabbit 1 (local.get $w) (struct.get $wabbit 2 (local.get $w))) (struct.set $wabbit 2 (local.get $w) (local.get $tmp)) (call $reverse (struct.get $wabbit 1 (local.get $w))) (call $reverse (struct.get $wabbit 2 (local.get $w)))))) (func (export "print") (call $pr (global.get $g))) (func $pr (param $w (ref null $wabbit)) (if (i32.eqz (ref.is_null (local.get $w))) (block (call $print_lp) (call $print_int (struct.get $wabbit 0 (local.get $w))) (call $pr (struct.get $wabbit 1 (local.get $w))) (call $pr (struct.get $wabbit 2 (local.get $w))) (call $print_rp)))) )`); let s = ""; function pr_int(k) { s += k + " "; } function pr_lp() { s += "(" }; function pr_rp() { s += ")" } let mod = new WebAssembly.Module(bin); let ins = new WebAssembly.Instance(mod, {"":{print_int:pr_int,print_lp:pr_lp,print_rp:pr_rp}}).exports; ins.init(6); s = ""; ins.print(); assertEq(s, "(0 (1 (2 (3 (4 )(5 ))(6 ))(7 (8 )(9 )))(10 (11 (12 )(13 ))(14 )))"); assertEq(ins.accumulate(), -13); ins.reverse(); s = ""; ins.print(); assertEq(s, "(0 (20 (28 )(22 (26 )(24 )))(2 (14 (18 )(16 ))(4 (12 )(6 (10 )(8 )))))"); assertEq(ins.accumulate(), 14); for (let i=10; i < 22; i++ ) { ins.init(i); ins.reverse(); gc(); ins.reverse(); } } // Sanity check for struct.set: we /can/ store a (ref null T) into a (ref null U) field // with struct.set if T <: U; this should fall out of normal coercion but good // to test. wasmEvalText( `(module (type $node (struct (field (mut (ref null $node))))) (type $nix (sub $node (struct (field (mut (ref null $node))) (field i32)))) (func $f (param $p (ref null $node)) (param $q (ref null $nix)) (struct.set $node 0 (local.get $p) (local.get $q))))`); // ref.cast: if the pointer is null we trap assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field i32))) (type $node2 (struct (field i32) (field f32))) (func $f (param $p (ref null $node)) (result (ref null $node2)) (ref.cast (ref $node2) (local.get $p))) (func (export "test") (result eqref) (call $f (ref.null $node))))`).exports.test(), WebAssembly.RuntimeError, /bad cast/, ); // ref.cast null: if the pointer is null we do not trap wasmEvalText( `(module (type $node (struct (field i32))) (type $node2 (struct (field i32) (field f32))) (func $f (param $p (ref null $node)) (result (ref null $node2)) (ref.cast (ref null $node2) (local.get $p))) (func (export "test") (result eqref) (call $f (ref.null $node))))`).exports.test(); // ref.cast: if the downcast succeeds we get the original pointer assertEq(wasmEvalText( `(module (type $node (struct (field i32))) (type $node2 (sub $node (struct (field i32) (field f32)))) (func $f (param $p (ref null $node)) (result (ref null $node2)) (ref.cast (ref null $node2) (local.get $p))) (func (export "test") (result i32) (local $n (ref null $node)) (local.set $n (struct.new $node2 (i32.const 0) (f32.const 12))) (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(), 1); // And once more with mutable fields assertEq(wasmEvalText( `(module (type $node (struct (field (mut i32)))) (type $node2 (sub $node (struct (field (mut i32)) (field f32)))) (func $f (param $p (ref null $node)) (result (ref null $node2)) (ref.cast (ref null $node2) (local.get $p))) (func (export "test") (result i32) (local $n (ref null $node)) (local.set $n (struct.new $node2 (i32.const 0) (f32.const 12))) (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(), 1); // ref.cast: eqref -> struct when the eqref is the right struct; // special case since eqref requires unboxing assertEq(wasmEvalText( `(module (type $node (struct (field i32))) (func $f (param $p eqref) (result (ref null $node)) (ref.cast (ref null $node) (local.get $p))) (func (export "test") (result i32) (local $n (ref null $node)) (local.set $n (struct.new $node (i32.const 0))) (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(), 1); // Can default initialize a struct which zero initializes { let {makeA, makeB, makeC} = wasmEvalText(` (module (type $a (struct)) (type $b (struct (field i32) (field f32))) (type $c (struct (field eqref))) (func (export "makeA") (result eqref) struct.new_default $a ) (func (export "makeB") (result eqref) struct.new_default $b ) (func (export "makeC") (result eqref) struct.new_default $c ) )`).exports; let a = makeA(); let b = makeB(); assertEq(b[0], 0); assertEq(b[1], 0); let c = makeC(); assertEq(c[0], null); } // struct.new_default: valid if all struct fields are defaultable wasmFailValidateText(`(module (type $a (struct (field (ref $a)))) (func struct.new_default $a ) )`, /defaultable/); wasmFailValidateText(`(module (type $a (struct (field i32) (field i32) (field (ref $a)))) (func struct.new_default $a ) )`, /defaultable/); // Negative tests // Attempting to mutate immutable field with struct.set assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field i32))) (func $f (param $p (ref null $node)) (struct.set $node 0 (local.get $p) (i32.const 37))))`), WebAssembly.CompileError, /field is not mutable/); // Attempting to store incompatible value in mutable field with struct.set assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field (mut i32)))) (func $f (param $p (ref null $node)) (struct.set $node 0 (local.get $p) (f32.const 37))))`), WebAssembly.CompileError, /expression has type f32 but expected i32/); // Out-of-bounds reference for struct.get assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field i32))) (func $f (param $p (ref null $node)) (result i32) (struct.get $node 1 (local.get $p))))`), WebAssembly.CompileError, /field index out of range/); // Out-of-bounds reference for struct.set assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field (mut i32)))) (func $f (param $p (ref null $node)) (struct.set $node 1 (local.get $p) (i32.const 37))))`), WebAssembly.CompileError, /field index out of range/); // Base pointer is of unrelated type to stated type in struct.get assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field i32))) (type $snort (struct (field f64))) (func $f (param $p (ref null $snort)) (result i32) (struct.get $node 0 (local.get $p))))`), WebAssembly.CompileError, /expression has type.*but expected.*/); // Base pointer is of unrelated type to stated type in struct.set assertErrorMessage(() => wasmEvalText( `(module (type $node (struct (field (mut i32)))) (type $snort (struct (field f64))) (func $f (param $p (ref null $snort)) (result i32) (struct.set $node 0 (local.get $p) (i32.const 0))))`), WebAssembly.CompileError, /expression has type.*but expected.*/); // Null pointer dereference in struct.get assertErrorMessage(function() { let ins = wasmEvalText( `(module (type $node (struct (field i32))) (func (export "test") (drop (call $f (ref.null $node)))) (func $f (param $p (ref null $node)) (result i32) (struct.get $node 0 (local.get $p))))`); ins.exports.test(); }, WebAssembly.RuntimeError, /dereferencing null pointer/); // Null pointer dereference in struct.set assertErrorMessage(function() { let ins = wasmEvalText( `(module (type $node (struct (field (mut i32)))) (func (export "test") (call $f (ref.null $node))) (func $f (param $p (ref null $node)) (struct.set $node 0 (local.get $p) (i32.const 0))))`); ins.exports.test(); }, WebAssembly.RuntimeError, /dereferencing null pointer/);