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

// The test needs --wasm-tier-up because we can't serialize and deserialize
// Liftoff code.
// Flags: --expose-wasm --allow-natives-syntax --expose-gc --wasm-tier-up

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

(function SerializeAndDeserializeModule() {
  print(arguments.callee.name);
  var builder = new WasmModuleBuilder();
  builder.addImportedMemory("", "memory", 1);
  var kSig_v_i = makeSig([kWasmI32], []);
  var signature = builder.addType(kSig_v_i);
  builder.addImport("", "some_value", kSig_i_v);
  builder.addImport("", "writer", signature);

  builder.addFunction("main", kSig_i_i)
    .addBody([
      kExprLocalGet, 0,
      kExprI32LoadMem, 0, 0,
      kExprI32Const, 1,
      kExprCallIndirect, signature, kTableZero,
      kExprLocalGet,0,
      kExprI32LoadMem,0, 0,
      kExprCallFunction, 0,
      kExprI32Add
    ]).exportFunc();

  // writer(mem[i]);
  // return mem[i] + some_value();
  builder.addFunction("_wrap_writer", signature)
    .addBody([
      kExprLocalGet, 0,
      kExprCallFunction, 1]);
  builder.appendToTable([2, 3]);

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var mem_1 = new WebAssembly.Memory({initial: 1});
  var view_1 = new Int32Array(mem_1.buffer);
  view_1[0] = 42;
  var outval_1;
  var i1 = new WebAssembly.Instance(module, {"":
                                             {some_value: () => 1,
                                              writer: (x) => outval_1 = x ,
                                              memory: mem_1}
                                            });

  assertEquals(43, i1.exports.main(0));

  assertEquals(42, outval_1);
  var buff = %SerializeWasmModule(module);
  module = null;
  gc();
  module = %DeserializeWasmModule(buff, wire_bytes);

  var mem_2 = new WebAssembly.Memory({initial: 2});
  var view_2 = new Int32Array(mem_2.buffer);

  view_2[0] = 50;

  var outval_2;
  var i2 = new WebAssembly.Instance(module, {"":
                                             {some_value: () => 1,
                                              writer: (x) => outval_2 = x ,
                                              memory: mem_2}
                                            });

  assertEquals(51, i2.exports.main(0));

  assertEquals(50, outval_2);
  // The instances don't share memory through deserialization.
  assertEquals(43, i1.exports.main(0));
})();

(function DeserializeInvalidObject() {
  print(arguments.callee.name);
  const invalid_buffer = new ArrayBuffer(10);
  const invalid_buffer_view = new Uint8Array(10);

  module = %DeserializeWasmModule(invalid_buffer, invalid_buffer_view);
  assertEquals(module, undefined);
})();

(function RelationBetweenModuleAndClone() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();
  builder.addFunction("main", kSig_i_v)
    .addBody([kExprI32Const, 42])
    .exportFunc();

  var wire_bytes = builder.toBuffer();
  var compiled_module = new WebAssembly.Module(wire_bytes);
  var serialized = %SerializeWasmModule(compiled_module);
  var clone = %DeserializeWasmModule(serialized, wire_bytes);

  assertNotNull(clone);
  assertFalse(clone == undefined);
  assertFalse(clone == compiled_module);
  assertEquals(clone.constructor, compiled_module.constructor);
})();

(function SerializeWrappersWithSameSignature() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();
  builder.addFunction("main", kSig_i_v)
    .addBody([kExprI32Const, 42])
    .exportFunc();
  builder.addFunction("main_same_signature", kSig_i_v)
    .addBody([kExprI32Const, 23])
    .exportFunc();

  var wire_bytes = builder.toBuffer();
  var compiled_module = new WebAssembly.Module(wire_bytes);
  var serialized = %SerializeWasmModule(compiled_module);
  var clone = %DeserializeWasmModule(serialized, wire_bytes);

  assertNotNull(clone);
  assertFalse(clone == undefined);
  assertFalse(clone == compiled_module);
  assertEquals(clone.constructor, compiled_module.constructor);
})();

(function SerializeAfterInstantiation() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();
  builder.addFunction("main", kSig_i_v)
    .addBody([kExprI32Const, 42])
    .exportFunc();

  var wire_bytes = builder.toBuffer()
  var compiled_module = new WebAssembly.Module(wire_bytes);
  var instance1 = new WebAssembly.Instance(compiled_module);
  var instance2 = new WebAssembly.Instance(compiled_module);
  var serialized = %SerializeWasmModule(compiled_module);
  var clone = %DeserializeWasmModule(serialized, wire_bytes);

  assertNotNull(clone);
  assertFalse(clone == undefined);
  assertFalse(clone == compiled_module);
  assertEquals(clone.constructor, compiled_module.constructor);
  var instance3 = new WebAssembly.Instance(clone);
  assertFalse(instance3 == undefined);
})();


(function SerializeAfterInstantiationWithMemory() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("", "memory", 1);
  builder.addFunction("main", kSig_i_v)
    .addBody([kExprI32Const, 42])
    .exportFunc();

  var wire_bytes = builder.toBuffer()
  var compiled_module = new WebAssembly.Module(wire_bytes);
  var mem_1 = new WebAssembly.Memory({initial: 1});
  var ffi =  {"":{memory:mem_1}};
  var instance1 = new WebAssembly.Instance(compiled_module, ffi);
  var serialized = %SerializeWasmModule(compiled_module);
  var clone = %DeserializeWasmModule(serialized, wire_bytes);

  assertNotNull(clone);
  assertFalse(clone == undefined);
  assertFalse(clone == compiled_module);
  assertEquals(clone.constructor, compiled_module.constructor);
  var instance2 = new WebAssembly.Instance(clone, ffi);
  assertFalse(instance2 == undefined);
})();

(function GlobalsArePrivateBetweenClones() {
  print(arguments.callee.name);
  var builder = new WasmModuleBuilder();
  builder.addGlobal(kWasmI32, true);
  builder.addFunction("read", kSig_i_v)
    .addBody([
      kExprGlobalGet, 0])
    .exportFunc();

  builder.addFunction("write", kSig_v_i)
    .addBody([
      kExprLocalGet, 0,
      kExprGlobalSet, 0])
    .exportFunc();

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var i1 = new WebAssembly.Instance(module);
  // serialize and replace module
  var buff = %SerializeWasmModule(module);
  var module_clone = %DeserializeWasmModule(buff, wire_bytes);
  var i2 = new WebAssembly.Instance(module_clone);
  i1.exports.write(1);
  i2.exports.write(2);
  assertEquals(1, i1.exports.read());
  assertEquals(2, i2.exports.read());
})();

(function SharedTableTest() {
  print(arguments.callee.name);
  let kTableSize = 3;
  var sig_index1;

  function MakeTableExportingModule(constant) {
    // A module that defines a table and exports it.
    var builder = new WasmModuleBuilder();
    builder.addType(kSig_i_i);
    builder.addType(kSig_i_ii);
    sig_index1 = builder.addType(kSig_i_v);
    var f1 = builder.addFunction("f1", sig_index1)
        .addBody([kExprI32Const, constant]);

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

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

    return new WebAssembly.Module(builder.toBuffer());
  }
  var m1 = MakeTableExportingModule(11);

  // 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([
      kExprLocalGet, 0,   // --
      kExprCallIndirect, sig_index2, kTableZero])  // --
    .exportAs("main");

  builder.addImportedTable("z", "table", kTableSize, kTableSize);
  builder.addElementSegment(0, 1, false, [f2.index]);
  var m2_bytes = builder.toBuffer();
  var m2 = new WebAssembly.Module(m2_bytes);

  assertFalse(sig_index1 == sig_index2);

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

  var serialized_m2 = %SerializeWasmModule(m2);
  var m2_clone = %DeserializeWasmModule(serialized_m2, m2_bytes);

  var m3 = MakeTableExportingModule(33);
  var i3 = new WebAssembly.Instance(m3);
  var i2_prime = new WebAssembly.Instance(m2_clone,
                                          {z: {table: i3.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));

  assertEquals(33, i3.exports.main(0));
  assertEquals(33, i2_prime.exports.main(0));

  assertThrows(() => i1.exports.main(2));
  assertThrows(() => i2.exports.main(2));
  assertThrows(() => i1.exports.main(3));
  assertThrows(() => i2.exports.main(3));
})();

(function StackOverflowAfterSerialization() {
  print(arguments.callee.name);
  const builder = new WasmModuleBuilder();
  var fun = builder.addFunction('main', kSig_v_v);
  fun.addBody([kExprCallFunction, fun.index]);
  fun.exportFunc();

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var buffer = %SerializeWasmModule(module);
  module = %DeserializeWasmModule(buffer, wire_bytes);
  var instance = new WebAssembly.Instance(module);

  assertThrows(instance.exports.main, RangeError);
})();

(function TrapAfterDeserialization() {
  print(arguments.callee.name);
  function GenerateSerializedModule() {
    const builder = new WasmModuleBuilder();
    builder.addMemory(1, 1);
    builder.addFunction('main', kSig_i_i)
        .addBody([kExprLocalGet, 0, kExprI32LoadMem, 0, 0])
        .exportFunc();
    const wire_bytes = builder.toBuffer();
    const module = new WebAssembly.Module(wire_bytes);
    const buffer = %SerializeWasmModule(module);
    return [wire_bytes, buffer];
  }
  const [wire_bytes, buffer] = GenerateSerializedModule();
  module = %DeserializeWasmModule(buffer, wire_bytes);
  const instance = new WebAssembly.Instance(module);

  assertEquals(0, instance.exports.main(0));
  assertEquals(0, instance.exports.main(kPageSize - 4));
  assertTraps(
      kTrapMemOutOfBounds, _ => instance.exports.main(kPageSize - 3));
})();

(function DirectCallAfterSerialization() {
  print(arguments.callee.name);
  const builder = new WasmModuleBuilder();
  var fun1 = builder.addFunction('fun1', kSig_i_v)
      .addBody([kExprI32Const, 23]);
  var fun2 = builder.addFunction('fun2', kSig_i_v)
      .addBody([kExprI32Const, 42]);
  builder.addFunction('main', kSig_i_v)
      .addBody([kExprCallFunction, fun1.index,
                kExprCallFunction, fun2.index,
                kExprI32Add])
      .exportFunc();

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var buffer = %SerializeWasmModule(module);
  module = %DeserializeWasmModule(buffer, wire_bytes);
  var instance = new WebAssembly.Instance(module);

  assertEquals(65, instance.exports.main());
})();

(function ImportCallAfterSerialization() {
  print(arguments.callee.name);
  const builder = new WasmModuleBuilder();
  var fun_import = builder.addImport("", "my_import", kSig_i_v);
  var fun = builder.addFunction('fun', kSig_i_v)
      .addBody([kExprI32Const, 23]);
  builder.addFunction('main', kSig_i_v)
      .addBody([kExprCallFunction, fun.index,
                kExprCallFunction, fun_import,
                kExprI32Add])
      .exportFunc();

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var buffer = %SerializeWasmModule(module);
  module = %DeserializeWasmModule(buffer, wire_bytes);
  var instance = new WebAssembly.Instance(module, {"": {my_import: () => 42 }});

  assertEquals(65, instance.exports.main());
})();

(function BranchTableAfterSerialization() {
  print(arguments.callee.name);
  const builder = new WasmModuleBuilder();
  builder.addFunction('main', kSig_i_i)
      .addBody([kExprBlock, kWasmVoid,
                  kExprBlock, kWasmVoid,
                    kExprBlock, kWasmVoid,
                      kExprBlock, kWasmVoid,
                        kExprBlock, kWasmVoid,
                          kExprBlock, kWasmVoid,
                            kExprBlock, kWasmVoid,
                              kExprLocalGet, 0,
                              kExprBrTable, 6, 0, 1, 2, 3, 4, 5, 6,
                            kExprEnd,
                            kExprI32Const, 3,
                            kExprReturn,
                          kExprEnd,
                          kExprI32Const, 7,
                          kExprReturn,
                        kExprEnd,
                        kExprI32Const, 9,
                        kExprReturn,
                      kExprEnd,
                      kExprI32Const, 11,
                      kExprReturn,
                    kExprEnd,
                    kExprI32Const, 23,
                    kExprReturn,
                  kExprEnd,
                  kExprI32Const, 35,
                  kExprReturn,
                kExprEnd,
                kExprI32Const, 42,
                kExprReturn])
      .exportFunc();

  var wire_bytes = builder.toBuffer();
  var module = new WebAssembly.Module(wire_bytes);
  var buffer = %SerializeWasmModule(module);
  module = %DeserializeWasmModule(buffer, wire_bytes);
  var instance = new WebAssembly.Instance(module);

  assertEquals(3, instance.exports.main(0));
  assertEquals(7, instance.exports.main(1));
  assertEquals(9, instance.exports.main(2));
  assertEquals(11, instance.exports.main(3));
  assertEquals(23, instance.exports.main(4));
  assertEquals(35, instance.exports.main(5));
  assertEquals(42, instance.exports.main(6));
  assertEquals(42, instance.exports.main(9));
})();
