// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");

let kMaxTableSize = 10000000;

function addFunctions(builder) {
  let sig_index = builder.addType(kSig_i_ii);
  let mul = builder.addFunction("mul", sig_index)
    .addBody([
      kExprLocalGet, 0,  // --
      kExprLocalGet, 1,  // --
      kExprI32Mul        // --
    ]);
  let add = builder.addFunction("add", sig_index)
    .addBody([
      kExprLocalGet, 0,  // --
      kExprLocalGet, 1,  // --
      kExprI32Add        // --
    ]);
  let sub = builder.addFunction("sub", sig_index)
    .addBody([
      kExprLocalGet, 0,  // --
      kExprLocalGet, 1,  // --
      kExprI32Sub        // --
    ]);
  return {mul: mul, add: add, sub: sub};
}

function testBounds(func, table) {
  for (let i = 0; i < table.length; i++) {
    assertEquals(0, func(i));
  }
  let l = table.length;
  let oob = [l, l + 1, l + 2, l * 2, l * 3, l + 10000];
  for (let i of oob) {
    assertThrows(() => func(i));
  }
}

function addMain(builder) {
  builder.addImportedTable("imp", "table", 0, kMaxTableSize);
  builder.addFunction("main", kSig_i_i)
    .addBody([
      kExprI32Const, 0,
      kExprLocalGet, 0,
      kExprCallIndirect, 0, kTableZero])
    .exportAs("main");
}

let id = (() => {  // identity exported function
  let builder = new WasmModuleBuilder();
  builder.addFunction("id", kSig_i_i)
    .addBody([kExprLocalGet, 0])
    .exportAs("id");
  let module = new WebAssembly.Module(builder.toBuffer());
  return (new WebAssembly.Instance(builder.toModule())).exports.id;
})();

(function TableGrowBoundsCheck() {
  print("TableGrowBoundsCheck");
  let builder = new WasmModuleBuilder();
  addMain(builder);
  let module = new WebAssembly.Module(builder.toBuffer());
  let table = new WebAssembly.Table({element: "anyfunc",
                                     initial: 1, maximum:kMaxTableSize});
  function fillTable() {
    for (let i = 0; i < table.length; i++) table.set(i, id);
    return table;
  }
  fillTable();
  let instance1 = new WebAssembly.Instance(module, {imp: {table:table}});
  testBounds(instance1.exports.main, table);

  for (let i = 0; i < 4; i++) {
    table.grow(1);
    fillTable(table);
    testBounds(instance1.exports.main, table);
  }
  let instance2 = new WebAssembly.Instance(module, {imp: {table:table}});
  testBounds(instance2.exports.main, table);

  for (let i = 0; i < 4; i++) {
    table.grow(1);
    fillTable(table);
    testBounds(instance1.exports.main, table);
    testBounds(instance2.exports.main, table);
  }
})();

(function TableGrowBoundsZeroInitial() {
  print("TableGrowBoundsZeroInitial");
  let builder = new WasmModuleBuilder();
  addMain(builder);
  let module = new WebAssembly.Module(builder.toBuffer());
  var table = new WebAssembly.Table({element: "anyfunc",
                                     initial: 0, maximum:kMaxTableSize});
  function growTableByOne() {
    table.grow(1);
    table.set(table.length - 1, id);
  }
  let instance1 = new WebAssembly.Instance(module, {imp: {table:table}});
  testBounds(instance1.exports.main, table);

  for (let i = 0; i < 4; i++) {
    growTableByOne();
    testBounds(instance1.exports.main, table);
  }
  let instance2 = new WebAssembly.Instance(module, {imp: {table:table}});
  testBounds(instance2.exports.main, table);

  for (let i = 0; i < 4; i++) {
    growTableByOne();
    testBounds(instance1.exports.main, table);
    testBounds(instance2.exports.main, table);
  }
})();

(function InstancesShareTableAndGrowTest() {
  print("InstancesShareTableAndGrowTest");
  let builder = new WasmModuleBuilder();
  let funcs = addFunctions(builder);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 15,  // --
      kExprLocalGet, 0,   // --
      kExprLocalGet, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  builder.addImportedTable("q", "table", 5, 32);
  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  builder.addActiveElementSegment(0, WasmInitExpr.GlobalGet(g),
      [funcs.mul.index, funcs.add.index, funcs.sub.index]);
  builder.addExportOfKind("table", kExternalTable, 0);
  let module = new WebAssembly.Module(builder.toBuffer());
  let t1 = new WebAssembly.Table({element: "anyfunc",
    initial: 5, maximum: 30});

  for (let i = 0; i < 5; i++) {
    print("base = " + i);
    let instance = new WebAssembly.Instance(module, {q: {base: i, table: t1}});
    main = instance.exports.main;
    assertEquals(i * 5 + 5, t1.length);

    // mul
    assertEquals(15, main(1, i));
    assertEquals(30, main(2, i));
    // add
    assertEquals(20, main(5, i+1));
    assertEquals(25, main(10, i+1));
    //sub
    assertEquals(10, main(5, i+2));
    assertEquals(5, main(10, i+2));

    assertThrows(() => t1.set(t1.length, id), RangeError);
    assertThrows(() => t1.set(t1.length + 5, id), RangeError);
    assertEquals(i * 5 + 5, t1.grow(5));
  }

  t1.set(t1.length - 1, id);
  assertThrows(() => t1.set(t1.length, id), RangeError);
  assertThrows(() => t1.grow(2), RangeError);
})();

(function ModulesShareTableAndGrowTest() {
  print("ModulesShareTableAndGrowTest");
  let builder = new WasmModuleBuilder();
  let sig_i_ii = builder.addType(kSig_i_ii);
  let sig_i_i = builder.addType(kSig_i_i);
  let sig_i_v = builder.addType(kSig_i_v);

  let g1 = builder.addImportedGlobal("q", "base", kWasmI32);

  let a = builder.addImport("q", "exp_add", sig_i_ii);
  let i = builder.addImport("q", "exp_inc", sig_i_i);
  let t = builder.addImport("q", "exp_ten", sig_i_v);

  builder.setTableBounds(7, 35);
  builder.addActiveElementSegment(0, WasmInitExpr.GlobalGet(g1), [a, i, t]);

  builder.addExportOfKind("table", kExternalTable, 0);
  let module = new WebAssembly.Module(builder.toBuffer());

  function exp_add(a, b) { return a + b; }
  function exp_inc(a) { return a + 1 | 0; }
  function exp_ten() { return 10; }

  let instance = new WebAssembly.Instance(module, {q: {base: 0,
    exp_add: exp_add, exp_inc: exp_inc, exp_ten: exp_ten}});

  let table = instance.exports.table;

  print("   initial check");

  function checkTableFunc(index, expected, ...args) {
    let f = table.get(index);
    print("  table[" + index + "] = " + f);
    result = f(...args);
    print("    -> expect " + expected + ", got " + result);
    assertEquals(expected, result);
  }

  checkTableFunc(0, 5, 1, 4);
  checkTableFunc(1, 9, 8);
  checkTableFunc(2, 10, 0);

  let builder1 = new WasmModuleBuilder();
  let g = builder1.addImportedGlobal("q", "base", kWasmI32);
  let funcs = addFunctions(builder1);

  builder1.addImportedTable("q", "table", 6, 36);
  builder1.addActiveElementSegment(
      0, WasmInitExpr.GlobalGet(g),
      [funcs.mul.index, funcs.add.index, funcs.sub.index]);
  let module1 = new WebAssembly.Module(builder1.toBuffer());

  function verifyTableFuncs(base) {
    print("  base = " + base);
    checkTableFunc(0, 5, 1, 4);
    checkTableFunc(1, 9, 8);
    checkTableFunc(2, 10, 0);

    checkTableFunc(base+0, 20, 10, 2); // mul
    checkTableFunc(base+1, 12, 10, 2); // add
    checkTableFunc(base+2,  8, 10, 2); // sub
  }

  for (let i = 3; i < 10; i++) {
    new WebAssembly.Instance(module1, {q: {base: i, table: table}});
    verifyTableFuncs(i);
    var prev = table.length;
    assertEquals(prev,     table.grow(3));
    assertEquals(prev + 3, table.length);
    verifyTableFuncs(i);

    assertThrows(() => table.set(table.length, id), RangeError);
    assertThrows(() => table.set(table.length + 5, id), RangeError);
  }
})();

(function ModulesInstancesSharedTableBoundsCheck() {
  print("ModulesInstancesSharedTableBoundsCheck");
  let table = new WebAssembly.Table({element: "anyfunc",
    initial: 1, maximum: kMaxTableSize});

  function CallModuleBuilder() {
    var builder = new WasmModuleBuilder();
    builder.addType(kSig_i_v);
    builder.addType(kSig_v_v);
    let index_i_ii = builder.addType(kSig_i_ii);
    let index_i_i = builder.addType(kSig_i_i);
    builder.addImportedTable("x", "table", 1, kMaxTableSize);
    builder.addFunction("add", index_i_ii)
      .addBody([
        kExprLocalGet, 0,
        kExprLocalGet, 1,
        kExprI32Add]);
    builder.addFunction("main", index_i_i)
      .addBody([
        kExprI32Const, 5,
        kExprI32Const, 5,
        kExprLocalGet, 0,
        kExprCallIndirect, index_i_ii, kTableZero])
      .exportAs("main");
    builder.addActiveElementSegment(0, WasmInitExpr.I32Const(0), [0]);
    return new WebAssembly.Module(builder.toBuffer());
  }

  var instances = [], modules = [];
  modules[0] = CallModuleBuilder();
  modules[1] = CallModuleBuilder();

  // Modules[0] shared by instances[0..2], modules[1] shared by instances[3, 4]
  instances[0] = new WebAssembly.Instance(modules[0], {x: {table:table}});
  instances[1] = new WebAssembly.Instance(modules[0], {x: {table:table}});
  instances[2] = new WebAssembly.Instance(modules[0], {x: {table:table}});
  instances[3] = new WebAssembly.Instance(modules[1], {x: {table:table}});
  instances[4] = new WebAssembly.Instance(modules[1], {x: {table:table}});

  function VerifyTableBoundsCheck(size) {
    print("Verifying bounds for size = " + size);
    assertEquals(size, table.length);
    for (let i = 0; i < 5; i++) {
      // Validity check for indirect call
      assertEquals(10, instances[i].exports.main(0));
      // Bounds check at different out of bounds indices
      assertInvalidFunction = function(s) {
        assertThrows(
            () => instances[i].exports.main(s), WebAssembly.RuntimeError,
            kTrapMsgs[kTrapTableOutOfBounds]);
      }
      assertInvalidFunction(size);
      assertInvalidFunction(size + 1);
      assertInvalidFunction(size + 1000);
      assertInvalidFunction(2 * size);
    }
  }

  for (let i = 0; i < 4; i++) {
    VerifyTableBoundsCheck(99900 * i + 1);
    table.grow(99900);
  }
})();