// |jit-test| skip-if: !wasmReftypesEnabled()

const N = 8;

function testTableFill(tbl_type, val_type, obj) {
  assertEq(obj.length, N);

  let ins
     = wasmEvalText(
        `(module
           (table 8 ${tbl_type})     ;; table 0
           (table $t 10 ${tbl_type}) ;; table 1

           ;; fill/get for table 0, referenced implicitly
           (func (export "fill0") (param $i i32) (param $r ${val_type}) (param $n i32)
             (table.fill (local.get $i) (local.get $r) (local.get $n))
           )
           (func (export "get0") (param $i i32) (result ${tbl_type})
             (table.get (local.get $i))
           )

           ;; fill/get for table 1, referenced explicitly
           (func (export "fill1") (param $i i32) (param $r ${val_type}) (param $n i32)
             (table.fill $t (local.get $i) (local.get $r) (local.get $n))
           )
           (func (export "get1") (param $i i32) (result ${tbl_type})
             (table.get $t (local.get $i))
           )
         )`);

  // An initial test to ascertain that tables 0 and 1 are independent

  // Fill in table 0, then check it.
  assertEq(ins.exports.fill0(2, obj[6], 5), undefined)
  assertEq(ins.exports.fill0(1, obj[7], 3), undefined);

  function check_table0() {
      assertEq(ins.exports.get0(0), null);
      assertEq(ins.exports.get0(1), obj[7]);
      assertEq(ins.exports.get0(2), obj[7]);
      assertEq(ins.exports.get0(3), obj[7]);
      assertEq(ins.exports.get0(4), obj[6]);
      assertEq(ins.exports.get0(5), obj[6]);
      assertEq(ins.exports.get0(6), obj[6]);
      assertEq(ins.exports.get0(7), null);
  }

  // Check that table 0 has the expected content.
  check_table0();

  // Check that messing with table 0 above hasn't changed table 1.
  for (let i = 0; i < 10; i++) {
      assertEq(ins.exports.get1(i), null);
  }

  // Now a bunch of tests involving only table 1.

  // Partly outside the table
  assertErrorMessage(() => ins.exports.fill1(8, obj[5], 3),
                     WebAssembly.RuntimeError, /index out of bounds/);

  assertEq(ins.exports.get1(7), null);
  assertEq(ins.exports.get1(8), null);
  assertEq(ins.exports.get1(9), null);

  // Within the table
  assertEq(ins.exports.fill1(2, obj[0], 3), undefined);
  assertEq(ins.exports.get1(1), null);
  assertEq(ins.exports.get1(2), obj[0]);
  assertEq(ins.exports.get1(3), obj[0]);
  assertEq(ins.exports.get1(4), obj[0]);
  assertEq(ins.exports.get1(5), null);

  // Within the table
  assertEq(ins.exports.fill1(4, obj[1], 2), undefined);
  assertEq(ins.exports.get1(3), obj[0]);
  assertEq(ins.exports.get1(4), obj[1]);
  assertEq(ins.exports.get1(5), obj[1]);
  assertEq(ins.exports.get1(6), null);

  // Within the table
  assertEq(ins.exports.fill1(4, obj[2], 0), undefined);
  assertEq(ins.exports.get1(3), obj[0]);
  assertEq(ins.exports.get1(4), obj[1]);
  assertEq(ins.exports.get1(5), obj[1]);

  // Within the table
  assertEq(ins.exports.fill1(8, obj[3], 2), undefined);
  assertEq(ins.exports.get1(7), null);
  assertEq(ins.exports.get1(8), obj[3]);
  assertEq(ins.exports.get1(9), obj[3]);

  // Within the table
  assertEq(ins.exports.fill1(9, null, 1), undefined);
  assertEq(ins.exports.get1(8), obj[3]);
  assertEq(ins.exports.get1(9), null);

  // Within the table
  assertEq(ins.exports.fill1(10, obj[4], 0), undefined);
  assertEq(ins.exports.get1(9), null);

  // Boundary tests on table 1: at the edge of the table.

  // Length-zero fill1 at the edge of the table must succeed
  assertEq(ins.exports.fill1(10, null, 0), undefined);

  // Length-one fill1 at the edge of the table fails
  assertErrorMessage(() => ins.exports.fill1(10, null, 1),
                     WebAssembly.RuntimeError, /index out of bounds/);

  // Length-more-than-one fill1 at the edge of the table fails
  assertErrorMessage(() => ins.exports.fill1(10, null, 2),
                     WebAssembly.RuntimeError, /index out of bounds/);


  // Boundary tests on table 1: beyond the edge of the table:

  // Length-zero fill1 beyond the edge of the table fails
  assertErrorMessage(() => ins.exports.fill1(11, null, 0),
                     WebAssembly.RuntimeError, /index out of bounds/);

  // Length-one fill1 beyond the edge of the table fails
  assertErrorMessage(() => ins.exports.fill1(11, null, 1),
                     WebAssembly.RuntimeError, /index out of bounds/);

  // Length-more-than-one fill1 beyond the edge of the table fails
  assertErrorMessage(() => ins.exports.fill1(11, null, 2),
                     WebAssembly.RuntimeError, /index out of bounds/);

  // Following all the above tests on table 1, check table 0 hasn't changed.
  check_table0();
}

var objs = [];
for (var i = 0; i < N; i++)
  objs[i] = {n:i};
testTableFill('externref', 'externref', objs);

var funcs = [];
for (var i = 0; i < N; i++)
  funcs[i] = wasmEvalText(`(module (func (export "x") (result i32) (i32.const ${i})))`).exports.x;
testTableFill('funcref', 'funcref', funcs);


// Type errors.  Required sig is: (i32, anyref, i32) -> void

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $expected-3-args-got-0
        (table.fill $t)
     ))`),
     WebAssembly.CompileError, /popping value from empty stack/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $expected-3-args-got-1
        (table.fill $t (i32.const 0))
     ))`),
     WebAssembly.CompileError, /popping value from empty stack/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $expected-3-args-got-2
        (table.fill $t (ref.null extern) (i32.const 0))
     ))`),
     WebAssembly.CompileError, /popping value from empty stack/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $argty-1-wrong
        (table.fill $t (i32.const 0) (ref.null extern) (f64.const 0))
     ))`),
     WebAssembly.CompileError,
     /type mismatch: expression has type f64 but expected i32/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $argty-2-wrong
        (table.fill $t (i32.const 0) (f32.const 0) (i32.const 0))
     ))`),
     WebAssembly.CompileError,
     /type mismatch: expression has type f32 but expected externref/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $argty-3-wrong
        (table.fill $t (i64.const 0) (ref.null extern) (i32.const 0))
     ))`),
     WebAssembly.CompileError,
     /type mismatch: expression has type i64 but expected i32/);

assertErrorMessage(() => wasmEvalText(
    `(module
      (table $t 10 anyref)
      (func $retty-wrong (result i32)
        (table.fill $t (i32.const 0) (ref.null extern) (i32.const 0))
     ))`),
     WebAssembly.CompileError,
     /popping value from empty stack/);

assertErrorMessage(() => wasmEvalText(
    `(module
       (table 8 funcref)
       (func (param $v anyref)
         (table.fill (i32.const 0) (local.get $v) (i32.const 0)))
     )`),
     WebAssembly.CompileError,
     /expression has type externref but expected funcref/);
