// |jit-test| test-also=--gc-zeal=2

// 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))
                    (then (struct.new $wabbit (local.get $tmp) (ref.null $wabbit) (ref.null $wabbit)))
                    (else
                      (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))
                    (then (i32.const 0))
                    (else
                      (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)))
                    (then
                      (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)))
                    (then 
                      (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 (sub (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 downcast succeeds we get the original pointer

assertEq(wasmEvalText(
  `(module
    (type $node (sub (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);

// 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();


// And once more with mutable fields

assertEq(wasmEvalText(
    `(module
      (type $node (sub (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(wasmGcReadField(b, 0), 0);
  assertEq(wasmGcReadField(b, 1), 0);

  let c = makeC();
  assertEq(wasmGcReadField(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/);
