///////////////////////////////////////////////////////////////////////////
//
// General table management in wasm

// Wasm: Create table-of-externref

new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref))`));

// Wasm: Import table-of-externref
// JS: create table-of-externref

new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table (import "m" "t") 10 externref))`)),
                         {m:{t: new WebAssembly.Table({element:"externref", initial:10})}});

new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (import "m" "t" (table 10 externref)))`)),
                         {m:{t: new WebAssembly.Table({element:"externref", initial:10})}});

// Wasm: Export table-of-externref, initial values shall be null

{
    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table (export "t") 10 externref))`)));
    let t = ins.exports.t;
    assertEq(t.length, 10);
    for (let i=0; i < t.length; i++)
        assertEq(t.get(0), null);
}

// JS: Exported table can be grown, and values are preserved

{
    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table (export "t") 10 externref))`)));
    let t = ins.exports.t;
    let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
    for (let i in objs)
        t.set(i, objs[i]);
    ins.exports.t.grow(10);
    assertEq(ins.exports.t.length, 20);
    for (let i in objs)
        assertEq(t.get(i), objs[i]);
}

// Wasm: table.copy between tables of externref (currently source and destination
// are both table zero)

{
    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table (export "t") 10 externref)
       (func (export "f")
         (table.copy (i32.const 5) (i32.const 0) (i32.const 3))))`)));
    let t = ins.exports.t;
    let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
    for (let i in objs)
        t.set(i, objs[i]);
    ins.exports.f();
    assertEq(t.get(0), objs[0]);
    assertEq(t.get(1), objs[1]);
    assertEq(t.get(2), objs[2]);
    assertEq(t.get(3), objs[3]);
    assertEq(t.get(4), objs[4]);
    assertEq(t.get(5), objs[0]);
    assertEq(t.get(6), objs[1]);
    assertEq(t.get(7), objs[2]);
    assertEq(t.get(8), objs[8]);
    assertEq(t.get(9), objs[9]);
}

// Wasm: table.copy from table(funcref) to table(externref) should not work

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
  `(module
     (table (export "t") 10 externref)
     (func $f1)
     (func $f2)
     (func $f3)
     (func $f4)
     (func $f5)
     (table 5 funcref)
     (elem (table 1) (i32.const 0) func $f1 $f2 $f3 $f4 $f5)
     (func (export "f")
       (table.copy 0 1 (i32.const 5) (i32.const 0) (i32.const 5))))`)),
    WebAssembly.CompileError,
    /(expression has type funcref but expected externref)|(type mismatch)/);

// Wasm: table.copy from table(externref) to table(funcref) should not work

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 funcref)
       (table 10 externref)
       (func (export "f")
         (table.copy 0 1 (i32.const 0) (i32.const 0) (i32.const 5))))`)),
                   WebAssembly.CompileError,
                   /(expression has type externref but expected funcref)|(type mismatch)/);

// Wasm: Element segments of funcref can't target tables of externref

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (func $f1 (result i32) (i32.const 0))
       (table (export "t") 10 externref)
       (elem 0 (i32.const 0) funcref (ref.func $f1)))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);


// Wasm: Element segments of externref can't target tables of funcref

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table (export "t") 10 funcref)
       (elem 0 (i32.const 0) externref (ref.null extern)))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);

// Wasm: table.init on table-of-externref is not allowed when the segment has
// funcref.

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (func $f1 (result i32) (i32.const 0))
       (table 10 externref)
       (elem funcref (ref.func $f1))
       (func
         (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
                   WebAssembly.CompileError,
                   /(expression has type funcref but expected externref)|(type mismatch)/);


// Wasm: table.init on table-of-funcref is not allowed when the segment has
// externref.

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 funcref)
       (elem externref (ref.null extern))
       (func
         (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
                   WebAssembly.CompileError,
                   /(expression has type externref but expected funcref)|(type mismatch)/);

// Wasm: table types must match at link time

assertErrorMessage(
    () => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
    `(module
       (import "m" "t" (table 10 externref)))`)),
                                   {m:{t: new WebAssembly.Table({element:"anyfunc", initial:10})}}),
    WebAssembly.LinkError,
    /imported table type mismatch/);

// call_indirect cannot reference table-of-externref

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref)
       (type $t (func (param i32) (result i32)))
       (func (result i32)
         (call_indirect (type $t) (i32.const 37))))`)),
                   WebAssembly.CompileError,
                   /(indirect calls must go through a table of 'funcref')|(indirect calls must go through a table of funcref)/);

///////////////////////////////////////////////////////////////////////////
//
// additional js api tests

{
    let tbl = new WebAssembly.Table({element:"externref", initial:10});

    // Initial value is undefined. This is different from when tables are
    // created inside wasm.
    assertEq(tbl.get(0), undefined);

    // Identity preserving.
    let x = {hi: 48};
    tbl.set(0, x);
    assertEq(tbl.get(0), x);
    tbl.set(2, dummy);
    assertEq(tbl.get(2), dummy);
    tbl.set(2, null);
    assertEq(tbl.get(2), null);

    // Temporary semantics is to convert to object and leave as object; once we
    // have a better wrapped externref this will change, we won't be able to
    // observe the boxing.
    tbl.set(1, 42);
    let y = tbl.get(1);
    assertEq(typeof y, "number");
    assertEq(y, 42);
}

function dummy() { return 37 }

///////////////////////////////////////////////////////////////////////////
//
// table.get and table.set

const wasmFun = wasmEvalText(`(module (func (export "x")))`).exports.x;

// table.get in bounds - returns right value type & value
// table.get out of bounds - fails

function testTableGet(type, x) {
    let ins = wasmEvalText(
        `(module
           (table (export "t") 10 ${type})
           (func (export "f") (param i32) (result ${type})
              (table.get (local.get 0))))`);
    ins.exports.t.set(0, x);
    assertEq(ins.exports.f(0), x);
    assertEq(ins.exports.f(1), null);
    assertErrorMessage(() => ins.exports.f(10), WebAssembly.RuntimeError, /index out of bounds/);
    assertErrorMessage(() => ins.exports.f(-5), WebAssembly.RuntimeError, /index out of bounds/);
}
testTableGet('externref', {});
testTableGet('funcref', wasmFun);

// table.get with non-i32 index - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref)
       (func (export "f") (param f64) (result externref)
         (table.get (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);

// table.get when there are no tables - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (func (export "f") (param i32)
         (drop (table.get (local.get 0)))))`)),
                   WebAssembly.CompileError,
                   /(table index out of range for table.get)|(table index out of bounds)/);

// table.set in bounds with i32 x externref - works, no value generated
// table.set with null - works
// table.set out of bounds - fails

function testTableSet(lhs_type, rhs_type, rhs_reftype, x) {
    let ins = wasmEvalText(
        `(module
           (table (export "t") 10 ${lhs_type})
           (func (export "set_ref") (param i32) (param ${rhs_type})
             (table.set (local.get 0) (local.get 1)))
           (func (export "set_null") (param i32)
             (table.set (local.get 0) (ref.null ${rhs_reftype}))))`);
    ins.exports.set_ref(3, x);
    assertEq(ins.exports.t.get(3), x);
    ins.exports.set_null(3);
    assertEq(ins.exports.t.get(3), null);

    assertErrorMessage(() => ins.exports.set_ref(10, x), WebAssembly.RuntimeError, /index out of bounds/);
    assertErrorMessage(() => ins.exports.set_ref(-1, x), WebAssembly.RuntimeError, /index out of bounds/);
}
testTableSet('externref', 'externref', 'extern', {});
testTableSet('funcref', 'funcref', 'func', wasmFun);

// Wasm: table.set on table(funcref) with externref value should fail

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
        `(module
           (table (export "t") 10 funcref)
           (func (export "set_ref") (param i32) (param externref)
             (table.set (local.get 0) (local.get 1))))`)),
                   WebAssembly.CompileError,
                   /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected funcref, found externref)/);

// table.set with non-i32 index - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref)
       (func (export "f") (param f64)
         (table.set (local.get 0) (ref.null extern))))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);

// table.set with non-ref value - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref)
       (func (export "f") (param f64)
         (table.set (i32.const 0) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 funcref)
       (func (export "f") (param f64)
         (table.set (i32.const 0) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);

// table.set when there are no tables - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
      (func (export "f") (param externref)
       (table.set (i32.const 0) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /(table index out of range for table.set)|(table index out of bounds)/);

function testTableGrow(lhs_type, lhs_reftype, rhs_type, x) {
  let ins = wasmEvalText(
      `(module
        (table (export "t") 10 20 ${lhs_type})
        (func (export "grow") (param i32) (result i32)
         (table.grow (ref.null ${lhs_reftype}) (local.get 0)))
        (func (export "grow2") (param i32) (param ${rhs_type}) (result i32)
         (table.grow (local.get 1) (local.get 0))))`);

  // we can grow table of references
  // table.grow with zero delta - always works even at maximum
  // table.grow with delta - works and returns correct old value
  // table.grow with delta at upper limit - fails
  // table.grow with negative delta - fails
  assertEq(ins.exports.grow(0), 10);
  assertEq(ins.exports.t.length, 10);
  assertEq(ins.exports.grow(1), 10);
  assertEq(ins.exports.t.length, 11);
  assertEq(ins.exports.t.get(10), null);
  assertEq(ins.exports.grow2(9, x), 11);
  assertEq(ins.exports.t.length, 20);
  for (var i = 11; i < 20; i++)
    assertEq(ins.exports.t.get(i), x);
  assertEq(ins.exports.grow(0), 20);

  // The JS API throws if it can't grow
  assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/);
  assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/);

  // The wasm API does not throw if it can't grow, but returns -1
  assertEq(ins.exports.grow(1), -1);
  assertEq(ins.exports.t.length, 20);
  assertEq(ins.exports.grow(-1), -1);
  assertEq(ins.exports.t.length, 20)
}
testTableGrow('externref', 'extern', 'externref', 42);
testTableGrow('funcref', 'func', 'funcref', wasmFun);

// Wasm: table.grow on table(funcref) with externref initializer should fail

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
      `(module
        (table (export "t") 10 20 funcref)
        (func (export "grow2") (param i32) (param externref) (result i32)
         (table.grow (local.get 1) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected funcref, found externref)/);

// Special case for private tables without a maximum

{
    let ins = wasmEvalText(
        `(module
          (table 10 externref)
          (func (export "grow") (param i32) (result i32)
           (table.grow (ref.null extern) (local.get 0))))`);
    assertEq(ins.exports.grow(0), 10);
    assertEq(ins.exports.grow(1), 10);
    assertEq(ins.exports.grow(9), 11);
    assertEq(ins.exports.grow(0), 20);
}

// table.grow with non-i32 argument - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (table 10 externref)
       (func (export "f") (param f64)
        (table.grow (ref.null extern) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /type mismatch/);

// table.grow when there are no tables - fails validation

assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
    `(module
       (func (export "f") (param i32)
        (table.grow (ref.null extern) (local.get 0))))`)),
                   WebAssembly.CompileError,
                   /(table index out of range for table.grow)|(table index out of bounds)/);

// table.size on table of externref

for (let visibility of ['', '(export "t")', '(import "m" "t")']) {
    let exp = {m:{t: new WebAssembly.Table({element:"externref",
                                            initial: 10,
                                            maximum: 20})}};
    let ins = wasmEvalText(
        `(module
          (table ${visibility} 10 20 externref)
          (func (export "grow") (param i32) (result i32)
           (table.grow (ref.null extern) (local.get 0)))
          (func (export "size") (result i32)
           (table.size)))`,
        exp);
    assertEq(ins.exports.grow(0), 10);
    assertEq(ins.exports.size(), 10);
    assertEq(ins.exports.grow(1), 10);
    assertEq(ins.exports.size(), 11);
    assertEq(ins.exports.grow(9), 11);
    assertEq(ins.exports.size(), 20);
    assertEq(ins.exports.grow(0), 20);
    assertEq(ins.exports.size(), 20);
}

// table.size on table of funcref

{
    let ins = wasmEvalText(
        `(module
          (table (export "t") 2 funcref)
          (func (export "f") (result i32)
           (table.size)))`);
    assertEq(ins.exports.f(), 2);
    ins.exports.t.grow(1);
    assertEq(ins.exports.f(), 3);
}
