// Copyright 2016 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.

// Flags: --expose-wasm --expose-gc

load("test/mjsunit/wasm/wasm-module-builder.js");

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

function js_div(a, b) { return (a / b) | 0; }

(function ExportedTableTest() {
  print(arguments.callee.name);

  let builder = new WasmModuleBuilder();

  let d = builder.addImport("q", "js_div", kSig_i_ii);
  let f = AddFunctions(builder);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 33,  // --
      kExprGetLocal, 0,   // --
      kExprGetLocal, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  f.add.exportAs("blarg");

  builder.setTableBounds(10, 10);
  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  builder.addElementSegment(
      0, g, true, [f.mul.index, f.add.index, f.sub.index, d]);
  builder.addExportOfKind("table", kExternalTable, 0);

  let module = new WebAssembly.Module(builder.toBuffer());

  for (let i = 0; i < 5; i++) {
    print(" base = " + i);
    let instance = new WebAssembly.Instance(module, {q: {base: i, js_div: js_div}});
    main = instance.exports.main;
    let table = instance.exports.table;
    assertTrue(table instanceof WebAssembly.Table);
    assertEquals(10, table.length);
    for (let j = 0; j < i; j++) {
      assertSame(null, table.get(j));
    }
    let mul = table.get(i+0);
    let add = table.get(i+1);
    let sub = table.get(i+2);

    print("  mul=" + mul);
    print("  add=" + add);
    print("  sub=" + sub);
    assertEquals("function", typeof mul);
    assertEquals("function", typeof add);
    assertEquals("function", typeof sub);
    assertEquals(2, mul.length);
    assertEquals(2, add.length);
    assertEquals(2, sub.length);
    assertEquals(String(f.add.index), add.name);

    let exp_div = table.get(i+3);
    assertEquals("function", typeof exp_div);
    print("  js_div=" + exp_div);
    // Should have a new, wrapped version of the import.
    assertFalse(js_div == exp_div);


    for (let j = i + 4; j < 10; j++) {
      assertSame(null, table.get(j));
    }

    assertEquals(-33, mul(-11, 3));
    assertEquals(4444444, add(3333333, 1111111));
    assertEquals(-9999, sub(1, 10000));
    assertEquals(-44, exp_div(-88.1, 2));
  }
})();


(function ImportedTableTest1() {
  let kTableSize = 10;
  print(arguments.callee.name);
  var builder = new WasmModuleBuilder();

  let d = builder.addImport("q", "js_div", kSig_i_ii);
  let f = AddFunctions(builder);
  builder.setTableBounds(kTableSize, kTableSize);
  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  builder.addElementSegment(
      0, g, true, [f.mul.index, f.add.index, f.sub.index, d]);
  builder.addExportOfKind("table", kExternalTable, 0);

  let m1 = new WebAssembly.Module(builder.toBuffer());

  var builder = new WasmModuleBuilder();

  builder.addImportedTable("r", "table", kTableSize, kTableSize);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 33,  // --
      kExprGetLocal, 0,   // --
      kExprGetLocal, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  let m2 = new WebAssembly.Module(builder.toBuffer());

  // Run 5 trials at different table bases.
  for (let i = 0; i < 5; i++) {
    print(" base = " + i);
    let i1 = new WebAssembly.Instance(m1, {q: {base: i, js_div: js_div}});
    let table = i1.exports.table;
    assertEquals(10, table.length);
    let i2 = new WebAssembly.Instance(m2, {r: {table: table}});
    let main = i2.exports.main;

    for (var j = 0; j < i; j++) {
      assertTraps(kTrapFuncSigMismatch, () => main(0, j));
      assertSame(null, table.get(j));
    }

    // mul
    assertEquals("function", typeof table.get(i+0));
    assertEquals(0, main(0, i+0));
    assertEquals(66, main(2, i+0));

    // add
    assertEquals("function", typeof table.get(i+1));
    assertEquals(33, main(0, i+1));
    assertEquals(38, main(5, i+1));

    // sub
    assertEquals("function", typeof table.get(i+2));
    assertEquals(32, main(1, i+2));
    assertEquals(28, main(5, i+2));

    // div
    assertEquals("function", typeof table.get(i+3));
    assertEquals(8, main(4, i+3));
    assertEquals(3, main(11, i+3));

    for (var j = i + 4; j < (kTableSize + 5); j++) {
      assertThrows(x => main(0, j));
      if (j < kTableSize) assertSame(null, table.get(j));
    }
  }
})();

(function ImportedTableTest2() {
  let kTableSize = 10;
  print(arguments.callee.name);

  var builder = new WasmModuleBuilder();

  let d = builder.addImport("q", "js_div", kSig_i_ii);
  builder.addImportedTable("q", "table", kTableSize, kTableSize);
  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  let f = AddFunctions(builder);
  builder.addElementSegment(
      0, g, true, [f.mul.index, f.add.index, f.sub.index, d]);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 55,  // --
      kExprGetLocal, 0,   // --
      kExprGetLocal, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  let m2 = new WebAssembly.Module(builder.toBuffer());

  // Run 5 trials at different table bases.
  for (let i = 0; i < 5; i++) {
    print(" base = " + i);
    let table = new WebAssembly.Table({element: "anyfunc",
                                       initial: kTableSize,
                                       maximum: kTableSize});
    assertEquals(10, table.length);
    let i2 = new WebAssembly.Instance(m2, {q: {base: i, table: table,
                                               js_div: js_div}});
    let main = i2.exports.main;

    for (var j = 0; j < i; j++) {
      assertTraps(kTrapFuncSigMismatch, () => main(0, j));
      assertSame(null, table.get(j));
    }

    // mul
    assertEquals("function", typeof table.get(i+0));
    assertEquals(0, main(0, i+0));
    assertEquals(110, main(2, i+0));

    // add
    assertEquals("function", typeof table.get(i+1));
    assertEquals(55, main(0, i+1));
    assertEquals(60, main(5, i+1));

    // sub
    assertEquals("function", typeof table.get(i+2));
    assertEquals(54, main(1, i+2));
    assertEquals(50, main(5, i+2));

    // div
    assertEquals("function", typeof table.get(i+3));
    assertEquals(13, main(4, i+3));
    assertEquals(5, main(11, i+3));

    for (var j = i + 4; j < (kTableSize + 5); j++) {
      assertThrows(x => main(0, j));
      if (j < kTableSize) assertSame(null, table.get(j));
    }
  }
})();


(function CumulativeTest() {
  print(arguments.callee.name);

  let kTableSize = 10;
  let table = new WebAssembly.Table(
    {element: "anyfunc", initial: kTableSize, maximum: kTableSize});

  var builder = new WasmModuleBuilder();

  builder.addImportedTable("x", "table", kTableSize, kTableSize);
  let g = builder.addImportedGlobal("x", "base", kWasmI32);
  let sig_index = builder.addType(kSig_i_v);
  let f = builder.addFunction("f", sig_index)
    .addBody([
      kExprGetGlobal, g
    ]);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprGetLocal, 0,
      kExprCallIndirect, sig_index, kTableZero])  // --
    .exportAs("main");
  builder.addElementSegment(0, g, true, [f.index]);

  let module = new WebAssembly.Module(builder.toBuffer());

  for (var i = 0; i < kTableSize; i++) {
    print(" base = " + i);
    let instance = new WebAssembly.Instance(module, {x: {base: i, table: table}});

    for (var j = 0; j < kTableSize; j++) {
      let func = table.get(j);
      if (j > i) {
        assertSame(null, func);
        assertTraps(kTrapFuncSigMismatch, () => instance.exports.main(j));
      } else {
        assertEquals("function", typeof func);
        assertEquals(j, func());
        assertEquals(j, instance.exports.main(j));
      }
    }
  }
})();

(function TwoWayTest() {
  print(arguments.callee.name);
  let kTableSize = 3;

  // Module {m1} defines the table and exports it.
  var builder = new WasmModuleBuilder();
  builder.addType(kSig_i_i);
  builder.addType(kSig_i_ii);
  var sig_index1 = builder.addType(kSig_i_v);
  var f1 = builder.addFunction("f1", sig_index1)
    .addBody([kExprI32Const, 11]);

  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprGetLocal, 0,   // --
      kExprCallIndirect, sig_index1, kTableZero])  // --
    .exportAs("main");

  builder.setTableBounds(kTableSize, kTableSize);
  builder.addElementSegment(0, 0, false, [f1.index]);
  builder.addExportOfKind("table", kExternalTable, 0);

  var m1 = new WebAssembly.Module(builder.toBuffer());

  // Module {m2} imports the table and adds {f2}.
  var builder = new WasmModuleBuilder();
  builder.addType(kSig_i_ii);
  var sig_index2 = builder.addType(kSig_i_v);
  var f2 = builder.addFunction("f2", sig_index2)
    .addBody([kExprI32Const, 22]);

  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprGetLocal, 0,   // --
      kExprCallIndirect, sig_index2, kTableZero])  // --
    .exportAs("main");

  builder.addImportedTable("z", "table", kTableSize, kTableSize);
  builder.addElementSegment(0, 1, false, [f2.index], true);

  var m2 = new WebAssembly.Module(builder.toBuffer());

  assertFalse(sig_index1 == sig_index2);

  var i1 = new WebAssembly.Instance(m1);
  var i2 = new WebAssembly.Instance(m2, {z: {table: i1.exports.table}});

  assertEquals(11, i1.exports.main(0));
  assertEquals(11, i2.exports.main(0));

  assertEquals(22, i1.exports.main(1));
  assertEquals(22, i2.exports.main(1));

  assertTraps(kTrapFuncSigMismatch, () => i1.exports.main(2));
  assertTraps(kTrapFuncSigMismatch, () => i2.exports.main(2));
  assertTraps(kTrapFuncInvalid, () => i1.exports.main(3));
  assertTraps(kTrapFuncInvalid, () => i2.exports.main(3));
})();

(function MismatchedTableSize() {
  print(arguments.callee.name);
  let kTableSize = 5;

  for (var expsize = 1; expsize < 4; expsize++) {
    for (var impsize = 1; impsize < 4; impsize++) {
      print(" expsize = " + expsize + ", impsize = " + impsize);
      var builder = new WasmModuleBuilder();
      builder.setTableBounds(expsize, expsize);
      builder.addExportOfKind("expfoo", kExternalTable, 0);

      let m1 = new WebAssembly.Module(builder.toBuffer());

      var builder = new WasmModuleBuilder();
      builder.addImportedTable("y", "impfoo", impsize, impsize);

      let m2 = new WebAssembly.Module(builder.toBuffer());

      var i1 = new WebAssembly.Instance(m1);

      // TODO(titzer): v8 currently requires import table size to match
      // export table size.
      var ffi = {y: {impfoo: i1.exports.expfoo}};
      if (expsize == impsize) {
        var i2 = new WebAssembly.Instance(m2, ffi);
      } else {
        assertThrows(() => new WebAssembly.Instance(m2, ffi));
      }
    }
  }
})();

(function TableGrowBoundsCheck() {
  print(arguments.callee.name);
  var kMaxSize = 30, kInitSize = 5;
  let table = new WebAssembly.Table({element: "anyfunc",
    initial: kInitSize, maximum: kMaxSize});
  var builder = new WasmModuleBuilder();
  builder.addImportedTable("x", "table", kInitSize, kMaxSize);
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module, {x: {base: 1, table: table}});

  for(var i = kInitSize; i < kMaxSize; i+=5) {
    assertEquals(i, table.length);
    for (var j = 0; j < i; j++) table.set(j, null);
    for (var j = 0; j < i; j++) assertEquals(null, table.get(j));
    assertThrows(() => table.set(i, null), RangeError);
    assertThrows(() => table.get(i), RangeError);
    assertEquals(i, table.grow(5));
  }
  assertEquals(30, table.length);
  assertThrows(() => table.grow(1), RangeError);
  assertThrows(() => table.set(kMaxSize, null), RangeError);
  assertThrows(() => table.get(kMaxSize), RangeError);
})();

(function CumulativeGrowTest() {
  print(arguments.callee.name);
  let table = new WebAssembly.Table({
    element: "anyfunc", initial: 10, maximum: 30});
  var builder = new WasmModuleBuilder();
  builder.addImportedTable("x", "table", 10, 30);

  let g = builder.addImportedGlobal("x", "base", kWasmI32);
  let sig_index = builder.addType(kSig_i_v);
  builder.addFunction("g", sig_index)
    .addBody([
      kExprGetGlobal, g
    ]);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprGetLocal, 0,
      kExprCallIndirect, sig_index, kTableZero])  // --
    .exportAs("main");
  builder.addElementSegment(0, g, true, [g]);
  let module = new WebAssembly.Module(builder.toBuffer());

  var instances = [];
  for (var i = 0; i < 10; i++) {
    print(" base = " + i);
    instances.push(new WebAssembly.Instance(
        module, {x: {base: i, table: table}}));
  }

  for (var j = 0; j < 10; j++) {
    let func = table.get(j);
    assertEquals("function", typeof func);
    assertEquals(j, func());
    assertEquals(j, instances[j].exports.main(j));
  }

  assertEquals(10, table.grow(10));

  // Verify that grow does not alter function behaviors
  for (var j = 0; j < 10; j++) {
    let func = table.get(j);
    assertEquals("function", typeof func);
    assertEquals(j, func());
    assertEquals(j, instances[j].exports.main(j));
  }

  let new_builder = new WasmModuleBuilder();
  new_builder.addExport("wasm", new_builder.addFunction("", kSig_v_v).addBody([]));
  new_builder.addImportedTable("x", "table", 20, 30);
  let new_module = new WebAssembly.Module(new_builder.toBuffer());
  let instance = new WebAssembly.Instance(new_module, {x: {table: table}});
  let new_func = instance.exports.wasm;

  for (var j = 10; j < 20; j++) {
    table.set(j, new_func);
    let func = table.get(j);
    assertEquals("function", typeof func);
    assertSame(new_func, table.get(j));
  }
  assertThrows(() => table.grow(11), RangeError);
})();


(function TestImportTooLarge() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();
  builder.addImportedTable("t", "t", 1, 2);

  // initial size is too large
  assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
    {element: "anyfunc", initial: 3, maximum: 3})}}), WebAssembly.LinkError);

  // maximum size is too large
  assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
    {element: "anyfunc", initial: 1, maximum: 4})}}), WebAssembly.LinkError);

  // no maximum
  assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
    {element: "anyfunc", initial: 1})}}), WebAssembly.LinkError);
})();

(function TableImportLargerThanCompiled() {
  print(arguments.callee.name);
  var kMaxSize = 30, kInitSize = 5;
  var builder = new WasmModuleBuilder();
  builder.addImportedTable("x", "table", 1, 35);
  let table = new WebAssembly.Table({element: "anyfunc",
    initial: kInitSize, maximum: kMaxSize});
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module, {x: {base: 1, table: table}});
  for (var i = 0; i < kInitSize; ++i) table.set(i, null);
  for (var i = 0; i < kInitSize; ++i) assertEquals(null, table.get(i));
  assertThrows(() => table.set(kInitSize, null), RangeError);
})();

(function ModulesShareTableAndGrow() {
  print(arguments.callee.name);
  let module1 = (() => {
    let builder = new WasmModuleBuilder();
    builder.addImportedTable("x", "table", 1, 35);
    return new WebAssembly.Module(builder.toBuffer());
  })();
  let module2 = (() => {
    let builder = new WasmModuleBuilder();
    builder.addImportedTable("x", "table", 2, 40);
    return new WebAssembly.Module(builder.toBuffer());
  })();

  var kMaxSize = 30, kInitSize = 5;
  let table = new WebAssembly.Table({element: "anyfunc",
    initial: kInitSize, maximum: kMaxSize});
  let instance1 = new WebAssembly.Instance(
      module1, {x: {base: 1, table: table}});
  let instance2 = new WebAssembly.Instance(
      module2, {x: {base: 1, table: table}});

  for (var i = 0; i < kInitSize; ++i) table.set(i, null);
  for (var i = 0; i < kInitSize; ++i) assertEquals(null, table.get(i));
  assertThrows(() => table.set(kInitSize, null), RangeError);
  assertEquals(kInitSize, table.grow(5));
  for (var i = 0; i < 2*kInitSize; ++i) table.set(i, null);
  for (var i = 0; i < 2*kInitSize; ++i) assertEquals(null, table.get(i));
  assertThrows(() => table.set(2*kInitSize, null), RangeError);
  // Try to grow past imported maximum
  assertThrows(() => table.grow(21), RangeError);
})();

(function MultipleElementSegments() {
  let kTableSize = 10;
  print(arguments.callee.name);

  let mul = (a, b) => a * b;
  let add = (a, b) => a + b;
  let sub = (a, b) => a - b;

  // Test 1 to 3 segments in the elements section.
  // segment 1 sets [1, 2] to mul,
  // segment 2 sets [2, 3, 4] to add,
  // segment 3 sets [3, 4, 5, 6] to sub.
  for (let num_segments = 1; num_segments < 4; ++num_segments) {
    var builder = new WasmModuleBuilder();

    builder.setTableBounds(kTableSize, kTableSize);
    builder.addExportOfKind("table", kExternalTable, 0);
    let f = AddFunctions(builder);
    let indexes = [f.mul.index, f.add.index, f.sub.index];
    for (let i = 0; i < num_segments; ++i) {
      let offset = i + 1;
      let len = i + 2;
      let index = indexes[i];
      builder.addElementSegment(0, offset, false, new Array(len).fill(index));
    }

    let instance = builder.instantiate();

    let table = instance.exports.table;
    assertEquals(kTableSize, table.length);

    for (let i = 0; i < num_segments; ++i) {
      let exp = i < 1 || i > 2 ? null : mul;
      if (num_segments > 1 && i >= 2 && i <= 4) exp = add;
      if (num_segments > 2 && i >= 3 && i <= 6) exp = sub;
      if (!exp) {
        assertSame(null, table.get(i));
      } else {
        assertEquals("function", typeof table.get(i));
    assertEquals(exp(6, 3), table.get(i)(6, 3));
      }
    }
  }
})();

(function InitImportedTableSignatureMismatch() {
  // instance0 exports a function table and a main function which indirectly
  // calls a function from the table.
  let builder0 = new WasmModuleBuilder();
  builder0.setName('module_0');
  let sig_index = builder0.addType(kSig_i_v);
  builder0.addFunction('main', kSig_i_i)
      .addBody([
        kExprGetLocal, 0,  // -
        kExprCallIndirect, sig_index, kTableZero
      ])
      .exportAs('main');
  builder0.setTableBounds(3, 3);
  builder0.addExportOfKind('table', kExternalTable);
  let module0 = new WebAssembly.Module(builder0.toBuffer());
  let instance0 = new WebAssembly.Instance(module0);

  // instance1 imports the table and adds a function to it.
  let builder1 = new WasmModuleBuilder();
  builder1.setName('module_1');
  builder1.addFunction('f', kSig_i_i).addBody([kExprGetLocal, 0]);
  builder1.addImportedTable('z', 'table');
  builder1.addElementSegment(0, 0, false, [0], true);
  let module1 = new WebAssembly.Module(builder1.toBuffer());
  let instance1 =
      new WebAssembly.Instance(module1, {z: {table: instance0.exports.table}});

  // Calling the main method on instance0 should fail, because the signature of
  // the added function does not match.
  assertThrows(
      () => instance0.exports.main(0), WebAssembly.RuntimeError,
      /signature mismatch/);
})();

(function IndirectCallIntoOtherInstance() {
  print(arguments.callee.name);

  var mem_1 = new WebAssembly.Memory({initial: 1});
  var mem_2 = new WebAssembly.Memory({initial: 1});
  var view_1 = new Int32Array(mem_1.buffer);
  var view_2 = new Int32Array(mem_2.buffer);
  view_1[0] = 1;
  view_2[0] = 1000;

  let builder = new WasmModuleBuilder();
  let sig = builder.addType(kSig_i_v);
  builder.addFunction('main', kSig_i_i)
    .addBody([kExprGetLocal, 0, kExprCallIndirect, sig, kTableZero])
    .exportAs('main');
  builder.addImportedMemory('', 'memory', 1);

  builder.setTableBounds(1, 1);
  builder.addExportOfKind('table', kExternalTable);

  let module1 = new WebAssembly.Module(builder.toBuffer());
  let instance1 = new WebAssembly.Instance(module1, {'':{memory:mem_1}});

  builder = new WasmModuleBuilder();
  builder.addFunction('main', kSig_i_v).addBody([kExprI32Const, 0, kExprI32LoadMem, 0, 0]);
  builder.addImportedTable('', 'table');
  builder.addElementSegment(0, 0, false, [0], true);
  builder.addImportedMemory('', 'memory', 1);


  let module2 = new WebAssembly.Module(builder.toBuffer());
  let instance2 = new WebAssembly.Instance(module2, {
    '': {
      table: instance1.exports.table,
      memory: mem_2
    }
  });

  assertEquals(instance1.exports.main(0), 1000);
})();


(function ImportedFreestandingTable() {
  print(arguments.callee.name);

  function forceGc() {
    gc();
    gc();
    gc();
  }

  function setup() {
    let builder = new WasmModuleBuilder();
    let sig = builder.addType(kSig_i_v);
    builder.addFunction('main', kSig_i_i)
      .addBody([kExprGetLocal, 0, kExprCallIndirect, sig, kTableZero])
      .exportAs('main');

    builder.addImportedTable('', 'table');

    let module1 = new WebAssembly.Module(builder.toBuffer());
    let table = new WebAssembly.Table({initial:2, element:'anyfunc'});
    let instance1 = new WebAssembly.Instance(module1, {'':{table: table}});

    builder = new WasmModuleBuilder();
    builder.addExport('theImport', builder.addImport('', 'callout', kSig_i_v));
    builder.addImportedMemory('', 'memory', 1);
    builder.addFunction('main', kSig_i_v)
      .addBody([
        kExprCallFunction, 0,
        kExprI32Const, 0, kExprI32LoadMem, 0, 0,
        kExprI32Add
      ]).exportAs('main');

    let mem = new WebAssembly.Memory({initial:1});
    let view = new Int32Array(mem.buffer);
    view[0] = 4;

    let module2 = new WebAssembly.Module(builder.toBuffer());
    let instance2 = new WebAssembly.Instance(module2, {
      '': {
        callout: () => {
          forceGc();
          return 3;
        },
        'memory': mem
      }
    });
    table.set(0, instance2.exports.main);
    table.set(1, instance2.exports.theImport);
    return instance1;
  }

  function test(variant, expectation) {
    var instance = setup();
    forceGc();
    assertEquals(expectation, instance.exports.main(variant));
  }

  // 0 indirectly calls the wasm function that calls the import,
  // 1 does the same but for the exported import.
  test(0, 7);
  test(1, 3);
})();


(function ImportedWasmFunctionPutIntoTable() {
  print(arguments.callee.name);

  let wasm_mul = (() => {
    let builder = new WasmModuleBuilder();
    builder.addFunction("mul", kSig_i_ii)
      .addBody(
        [kExprGetLocal, 0,
         kExprGetLocal, 1,
         kExprI32Mul])
      .exportFunc();
    return builder.instantiate().exports.mul;
  })();

  let builder = new WasmModuleBuilder();

  let j = builder.addImport("q", "js_div", kSig_i_ii);
  let w = builder.addImport("q", "wasm_mul", kSig_i_ii);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 33,  // --
      kExprGetLocal, 0,   // --
      kExprGetLocal, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  builder.setTableBounds(10, 10);
  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  builder.addElementSegment(0, g, true, [j, w]);

  let module = new WebAssembly.Module(builder.toBuffer());
  for (var i = 0; i < 5; i++) {
    let instance = new WebAssembly.Instance(module, {q: {base: i, js_div: js_div, wasm_mul: wasm_mul}});
    let j = i + 1;

    assertThrows(() => {instance.exports.main(j, i-1)});
    assertEquals((33/j)|0, instance.exports.main(j, i+0));
    assertEquals((33*j)|0, instance.exports.main(j, i+1));
    assertThrows(() => {instance.exports.main(j, i+2)});
  }

})();

(function ImportedWasmFunctionPutIntoImportedTable() {
  print(arguments.callee.name);

  let kTableSize = 10;

  let wasm_mul = (() => {
    let builder = new WasmModuleBuilder();
    builder.addFunction("mul", kSig_i_ii)
      .addBody(
        [kExprGetLocal, 0,
         kExprGetLocal, 1,
         kExprI32Mul])
      .exportFunc();
    return builder.instantiate().exports.mul;
  })();

  let table = new WebAssembly.Table({element: "anyfunc",
                                     initial: kTableSize,
                                     maximum: kTableSize});

  let builder = new WasmModuleBuilder();

  let j = builder.addImport("q", "js_div", kSig_i_ii);
  let w = builder.addImport("q", "wasm_mul", kSig_i_ii);
  builder.addImportedTable("q", "table", kTableSize, kTableSize);
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprI32Const, 44,  // --
      kExprGetLocal, 0,   // --
      kExprGetLocal, 1,   // --
      kExprCallIndirect, 0, kTableZero])  // --
    .exportAs("main");

  let g = builder.addImportedGlobal("q", "base", kWasmI32);
  builder.addElementSegment(0, g, true, [j, w]);

  let module = new WebAssembly.Module(builder.toBuffer());
  for (var i = 0; i < 5; i++) {
    let instance = new WebAssembly.Instance(module, {q: {base: i, js_div: js_div, wasm_mul: wasm_mul, table: table}});
    let j = i + 1;

    assertEquals((44/j)|0, instance.exports.main(j, i+0));
    assertEquals((44*j)|0, instance.exports.main(j, i+1));
    assertThrows(() => {instance.exports.main(j, i+2)});
  }
})();

(function ExportedFunctionsImportedOrder() {
  print(arguments.callee.name);

  let i1 = (() => {
    let builder = new WasmModuleBuilder();
    builder.addFunction("f1", kSig_i_v)
      .addBody(
        [kExprI32Const, 1])
      .exportFunc();
    builder.addFunction("f2", kSig_i_v)
      .addBody(
        [kExprI32Const, 2])
      .exportFunc();
    return builder.instantiate();
  })();

  let i2 = (() => {
    let builder = new WasmModuleBuilder();
    builder.addImport("q", "f2", kSig_i_v);
    builder.addImport("q", "f1", kSig_i_v);
    builder.addFunction("main", kSig_i_i)
      .addBody([
        kExprGetLocal, 0,
        kExprCallIndirect, 0, kTableZero
      ])
      .exportFunc();
    builder.addElementSegment(0, 0, false, [0, 1, 1, 0]);

    return builder.instantiate({q: {f2: i1.exports.f2, f1: i1.exports.f1}});
  })();

  assertEquals(2, i2.exports.main(0));
  assertEquals(1, i2.exports.main(1));
  assertEquals(1, i2.exports.main(2));
  assertEquals(2, i2.exports.main(3));
})();

(function IndirectCallsToImportedFunctions() {
  print(arguments.callee.name);

  let module = (() => {
    let builder = new WasmModuleBuilder();
    builder.addMemory(1, 1, false);
    builder.addFunction("f", kSig_i_v)
      .addBody([
        kExprI32Const, 0,
        kExprI32LoadMem, 0, 4,
      ])
      .exportFunc();
    builder.exportMemoryAs("memory");
    return new WebAssembly.Module(builder.toBuffer());
  })();

  function setMemI32(instance, offset, val) {
    var array = new Int32Array(instance.exports.memory.buffer);
    array[offset/4] = val;
  }

  function makeFun(val) {
    let instance = new WebAssembly.Instance(module);
    setMemI32(instance, 0, 2000000);
    setMemI32(instance, 4, val);
    setMemI32(instance, 8, 3000000);
    return instance.exports.f;
  }

  let f300 = makeFun(300);
  let f100 = makeFun(100);
  let f200 = makeFun(200);

  let main = (() => {
    let builder = new WasmModuleBuilder();
    builder.addMemory(1, 1, false);
    builder.addImport("q", "f1", kSig_i_v);
    builder.addImport("q", "f2", kSig_i_v);
    builder.addImport("q", "f3", kSig_i_v);
    builder.addFunction("f", kSig_i_v)
      .addBody([
        kExprI32Const, 8,
        kExprI32LoadMem, 0, 0,
      ]);
    builder.addFunction("main", kSig_i_i)
      .addBody([
        kExprGetLocal, 0,
        kExprCallIndirect, 0, kTableZero
      ])
      .exportFunc();
    builder.exportMemoryAs("memory");
    builder.addElementSegment(0, 0, false, [0, 1, 2, 3]);
    var instance = builder.instantiate({q: {f1: f100, f2: f200, f3: f300}});
    setMemI32(instance, 0, 5000000);
    setMemI32(instance, 4, 6000000);
    setMemI32(instance, 8, 400);
    return instance.exports.main;
  })();

  assertEquals(100, main(0));
  assertEquals(200, main(1));
  assertEquals(300, main(2));
  assertEquals(400, main(3));
})();