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

// Flags: --experimental-wasm-threads

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

const kMemtypeSize32 = 4;
const kMemtypeSize16 = 2;
const kMemtypeSize8 = 1;

function Add(a, b) { return a + b; }
function Sub(a, b) { return a - b; }
function And(a, b) { return a & b; }
function Or(a, b) { return a | b; }
function Xor(a, b) { return a ^ b; }
function Exchange(a, b) { return b; }

let maxSize = 10;
let memory = new WebAssembly.Memory({initial: 1, maximum: maxSize, shared: true});

function GetAtomicBinOpFunction(wasmExpression, alignment, offset) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kAtomicPrefix,
      wasmExpression, alignment, offset])
    .exportAs("main");

  // Instantiate module, get function exports
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module,
        {m: {imported_mem: memory}});
  return instance.exports.main;
}

function GetAtomicCmpExchangeFunction(wasmExpression, alignment, offset) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
  builder.addFunction("main", kSig_i_iii)
    .addBody([
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kExprLocalGet, 2,
      kAtomicPrefix,
      wasmExpression, alignment, ...wasmSignedLeb(offset, 5)])
    .exportAs("main");

  // Instantiate module, get function exports
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module,
        {m: {imported_mem: memory}});
  return instance.exports.main;
}

function GetAtomicLoadFunction(wasmExpression, alignment, offset) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
  builder.addFunction("main", kSig_i_i)
    .addBody([
      kExprLocalGet, 0,
      kAtomicPrefix,
      wasmExpression, alignment, offset])
    .exportAs("main");

  // Instantiate module, get function exports
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module,
        {m: {imported_mem: memory}});
  return instance.exports.main;
}

function GetAtomicStoreFunction(wasmExpression, alignment, offset) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
  builder.addFunction("main", kSig_v_ii)
    .addBody([
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kAtomicPrefix,
      wasmExpression, alignment, offset])
    .exportAs("main");

  // Instantiate module, get function exports
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module,
        {m: {imported_mem: memory}});
  return instance.exports.main;
}

function VerifyBoundsCheck(func, memtype_size) {
  const kPageSize = 65536;
  // Test out of bounds at boundary
  for (let i = memory.buffer.byteLength - memtype_size + 1;
       i < memory.buffer.byteLength + memtype_size + 4; i++) {
    assertTraps(kTrapMemOutOfBounds, () => func(i, 5, 10));
  }
  // Test out of bounds at maximum + 1
  assertTraps(kTrapMemOutOfBounds, () => func((maxSize + 1) * kPageSize, 5, 1));
}

// Test many elements in the small range, make bigger steps later. This is still
// O(2^n), but takes 213 steps to reach 2^32.
const inc = i => i + Math.floor(i/10) + 1;

function Test32Op(operation, func) {
  let i32 = new Uint32Array(memory.buffer);
  for (let i = 0; i < i32.length; i = inc(i)) {
    let expected = 0x9cedf00d;
    let value = 0x11111111;
    i32[i] = expected;
    assertEquals(expected, func(i * kMemtypeSize32, value) >>> 0);
    assertEquals(operation(expected, value) >>> 0, i32[i]);
  }
  VerifyBoundsCheck(func, kMemtypeSize32);
}

function Test16Op(operation, func) {
  let i16 = new Uint16Array(memory.buffer);
  for (let i = 0; i < i16.length; i = inc(i)) {
    let expected = 0xd00d;
    let value = 0x1111;
    i16[i] = expected;
    assertEquals(expected, func(i * kMemtypeSize16, value));
    assertEquals(operation(expected, value), i16[i]);
  }
  VerifyBoundsCheck(func, kMemtypeSize16);
}

function Test8Op(operation, func) {
  let i8 = new Uint8Array(memory.buffer);
  for (let i = 0; i < i8.length; i = inc(i)) {
    let expected = 0xbe;
    let value = 0x12;
    i8[i] = expected;
    assertEquals(expected, func(i * kMemtypeSize8, value));
    assertEquals(operation(expected, value), i8[i]);
  }
  VerifyBoundsCheck(func, kMemtypeSize8, 10);
}

(function TestAtomicAdd() {
  print(arguments.callee.name);
  let wasmAdd = GetAtomicBinOpFunction(kExprI32AtomicAdd, 2, 0);
  Test32Op(Add, wasmAdd);
})();

(function TestAtomicAdd16U() {
  print(arguments.callee.name);
  let wasmAdd = GetAtomicBinOpFunction(kExprI32AtomicAdd16U, 1, 0);
  Test16Op(Add, wasmAdd);
})();

(function TestAtomicAdd8U() {
  print(arguments.callee.name);
  let wasmAdd = GetAtomicBinOpFunction(kExprI32AtomicAdd8U, 0, 0);
  Test8Op(Add, wasmAdd);
})();

(function TestAtomicSub() {
  print(arguments.callee.name);
  let wasmSub = GetAtomicBinOpFunction(kExprI32AtomicSub, 2, 0);
  Test32Op(Sub, wasmSub);
})();

(function TestAtomicSub16U() {
  print(arguments.callee.name);
  let wasmSub = GetAtomicBinOpFunction(kExprI32AtomicSub16U, 1, 0);
  Test16Op(Sub, wasmSub);
})();

(function TestAtomicSub8U() {
  print(arguments.callee.name);
  let wasmSub = GetAtomicBinOpFunction(kExprI32AtomicSub8U, 0, 0);
  Test8Op(Sub, wasmSub);
})();

(function TestAtomicAnd() {
  print(arguments.callee.name);
  let wasmAnd = GetAtomicBinOpFunction(kExprI32AtomicAnd, 2, 0);
  Test32Op(And, wasmAnd);
})();

(function TestAtomicAnd16U() {
  print(arguments.callee.name);
  let wasmAnd = GetAtomicBinOpFunction(kExprI32AtomicAnd16U, 1, 0);
  Test16Op(And, wasmAnd);
})();

(function TestAtomicAnd8U() {
  print(arguments.callee.name);
  let wasmAnd = GetAtomicBinOpFunction(kExprI32AtomicAnd8U, 0, 0);
  Test8Op(And, wasmAnd);
})();

(function TestAtomicOr() {
  print(arguments.callee.name);
  let wasmOr = GetAtomicBinOpFunction(kExprI32AtomicOr, 2, 0);
  Test32Op(Or, wasmOr);
})();

(function TestAtomicOr16U() {
  print(arguments.callee.name);
  let wasmOr = GetAtomicBinOpFunction(kExprI32AtomicOr16U, 1, 0);
  Test16Op(Or, wasmOr);
})();

(function TestAtomicOr8U() {
  print(arguments.callee.name);
  let wasmOr = GetAtomicBinOpFunction(kExprI32AtomicOr8U, 0, 0);
  Test8Op(Or, wasmOr);
})();

(function TestAtomicXor() {
  print(arguments.callee.name);
  let wasmXor = GetAtomicBinOpFunction(kExprI32AtomicXor, 2, 0);
  Test32Op(Xor, wasmXor);
})();

(function TestAtomicXor16U() {
  print(arguments.callee.name);
  let wasmXor = GetAtomicBinOpFunction(kExprI32AtomicXor16U, 1, 0);
  Test16Op(Xor, wasmXor);
})();

(function TestAtomicXor8U() {
  print(arguments.callee.name);
  let wasmXor = GetAtomicBinOpFunction(kExprI32AtomicXor8U, 0, 0);
  Test8Op(Xor, wasmXor);
})();

(function TestAtomicExchange() {
  print(arguments.callee.name);
  let wasmExchange = GetAtomicBinOpFunction(kExprI32AtomicExchange, 2, 0);
  Test32Op(Exchange, wasmExchange);
})();

(function TestAtomicExchange16U() {
  print(arguments.callee.name);
  let wasmExchange = GetAtomicBinOpFunction(kExprI32AtomicExchange16U, 1, 0);
  Test16Op(Exchange, wasmExchange);
})();

(function TestAtomicExchange8U() {
  print(arguments.callee.name);
  let wasmExchange = GetAtomicBinOpFunction(kExprI32AtomicExchange8U, 0, 0);
  Test8Op(Exchange, wasmExchange);
})();

function TestCmpExchange(func, buffer, params, size, offset = 0) {
  for (let i = 0; i + (offset / size) < buffer.length; i = inc(i)) {
    for (let j = 0; j < params.length; j++) {
      for (let k = 0; k < params.length; k++) {
        buffer[i + (offset / size)] = params[j];
        let loaded = func(i * size, params[k], params[j]) >>> 0;
        let expected = (params[k] == loaded) ? params[j] : loaded;
        assertEquals(loaded, params[j]);
        assertEquals(expected, buffer[i + (offset / size)]);
      }
    }
  }
  VerifyBoundsCheck(func, size);
}

(function TestAtomicCompareExchange() {
  print(arguments.callee.name);
  // Offset is big enough to not fit in a 12-bit immediate on arm64, but small
  // enough to fit in the maxSize wasm pages.
  const offset = 0x1234;
  let wasmCmpExchange =
      GetAtomicCmpExchangeFunction(kExprI32AtomicCompareExchange, 2, offset);
  let i32 = new Uint32Array(memory.buffer);
  let params = [0x00000001, 0x00000555, 0x00099999, 0xffffffff];
  TestCmpExchange(wasmCmpExchange, i32, params, kMemtypeSize32, offset);
})();

(function TestAtomicCompareExchange16U() {
  print(arguments.callee.name);
  let wasmCmpExchange =
      GetAtomicCmpExchangeFunction(kExprI32AtomicCompareExchange16U, 1, 0);
  let i16 = new Uint16Array(memory.buffer);
  let params = [0x0001, 0x0555, 0x9999];
  TestCmpExchange(wasmCmpExchange, i16, params, kMemtypeSize16);
})();

(function TestAtomicCompareExchange8U() {
  print(arguments.callee.name);
  let wasmCmpExchange =
      GetAtomicCmpExchangeFunction(kExprI32AtomicCompareExchange8U, 0, 0);
  let i8 = new Uint8Array(memory.buffer);
  let params = [0x01, 0x0d, 0xf9];
  TestCmpExchange(wasmCmpExchange, i8, params, kMemtypeSize8);
})();

function TestLoad(func, buffer, value, size) {
  for (let i = 0; i < buffer.length; i = inc(i)) {
    buffer[i] = value;
    assertEquals(value, func(i * size) >>> 0);
  }
  VerifyBoundsCheck(func, size);
}

(function TestAtomicLoad() {
  print(arguments.callee.name);
  let wasmLoad = GetAtomicLoadFunction(kExprI32AtomicLoad, 2, 0);
  let i32 = new Uint32Array(memory.buffer);
  let value = 0xacedaced;
  TestLoad(wasmLoad, i32, value, kMemtypeSize32);
})();

(function TestAtomicLoad16U() {
  print(arguments.callee.name);
  let wasmLoad = GetAtomicLoadFunction(kExprI32AtomicLoad16U, 1, 0);
  let i16 = new Uint16Array(memory.buffer);
  let value = 0xaced;
  TestLoad(wasmLoad, i16, value, kMemtypeSize16);
})();

(function TestAtomicLoad8U() {
  print(arguments.callee.name);
  let wasmLoad = GetAtomicLoadFunction(kExprI32AtomicLoad8U, 0, 0);
  let i8 = new Uint8Array(memory.buffer);
  let value = 0xac;
  TestLoad(wasmLoad, i8, value, kMemtypeSize8);
})();

function TestStore(func, buffer, value, size) {
  for (let i = 0; i < buffer.length; i = inc(i)) {
    func(i * size, value)
    assertEquals(value, buffer[i]);
  }
  VerifyBoundsCheck(func, size);
}

(function TestAtomicStore() {
  print(arguments.callee.name);
  let wasmStore = GetAtomicStoreFunction(kExprI32AtomicStore, 2, 0);
  let i32 = new Uint32Array(memory.buffer);
  let value = 0xacedaced;
  TestStore(wasmStore, i32, value, kMemtypeSize32);
})();

(function TestAtomicStore16U() {
  print(arguments.callee.name);
  let wasmStore = GetAtomicStoreFunction(kExprI32AtomicStore16U, 1, 0);
  let i16 = new Uint16Array(memory.buffer);
  let value = 0xaced;
  TestStore(wasmStore, i16, value, kMemtypeSize16);
})();

(function TestAtomicStore8U() {
  print(arguments.callee.name);
  let wasmStore = GetAtomicStoreFunction(kExprI32AtomicStore8U, 0, 0);
  let i8 = new Uint8Array(memory.buffer);
  let value = 0xac;
  TestCmpExchange(wasmStore, i8, value, kMemtypeSize8);
})();

(function TestAtomicLoadStoreOffset() {
  print(arguments.callee.name);
  var builder = new WasmModuleBuilder();
  let memory = new WebAssembly.Memory({
    initial: 16, maximum: 128, shared: true});
  builder.addImportedMemory("m", "imported_mem", 16, 128, "shared");
  builder.addFunction("loadStore", kSig_i_v)
    .addBody([
      kExprI32Const, 16,
      kExprI32Const, 20,
      kAtomicPrefix,
      kExprI32AtomicStore, 0, 0xFC, 0xFF, 0x3a,
      kExprI32Const, 16,
      kAtomicPrefix,
      kExprI32AtomicLoad, 0, 0xFC, 0xFF, 0x3a])
    .exportAs("loadStore");
  builder.addFunction("storeOob", kSig_v_v)
    .addBody([
      kExprI32Const, 16,
      kExprI32Const, 20,
      kAtomicPrefix,
      kExprI32AtomicStore, 0, 0xFC, 0xFF, 0xFF, 0x3a])
    .exportAs("storeOob");
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = (new WebAssembly.Instance(module,
        {m: {imported_mem: memory}}));
  let buf = memory.buffer;
  assertEquals(20, instance.exports.loadStore());
  assertTraps(kTrapMemOutOfBounds, instance.exports.storeOob);
})();

(function TestAtomicOpinLoop() {
  print(arguments.callee.name);
  var builder = new WasmModuleBuilder();
  let memory = new WebAssembly.Memory({
    initial: 16, maximum: 128, shared: true});
  builder.addImportedMemory("m", "imported_mem", 16, 128, "shared");
  builder.addFunction("main", kSig_i_v)
    .addBody([
      kExprLoop, kWasmStmt,
        kExprI32Const, 16,
        kExprI32Const, 20,
        kAtomicPrefix,
        kExprI32AtomicStore, 2, 0,
        kExprI32Const, 16,
        kAtomicPrefix,
        kExprI32AtomicLoad, 2, 0,
        kExprReturn,
      kExprEnd,
      kExprI32Const, 0
    ])
    .exportFunc();
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = (new WebAssembly.Instance(module,
        {m: {imported_mem: memory}}));
  assertEquals(20, instance.exports.main());
})();

(function TestUnalignedAtomicAccesses() {
  print(arguments.callee.name);
  let wasmAdd = GetAtomicBinOpFunction(kExprI32AtomicAdd, 2, 17);
  assertTraps(kTrapUnalignedAccess, () => wasmAdd(4, 1001));
  let wasmLoad = GetAtomicLoadFunction(kExprI32AtomicLoad16U, 1, 0);
  assertTraps(kTrapUnalignedAccess, () => wasmLoad(15));
  let wasmStore = GetAtomicStoreFunction(kExprI32AtomicStore, 2, 0);
  assertTraps(kTrapUnalignedAccess, () => wasmStore(22, 5));
  let wasmCmpExchange =
      GetAtomicCmpExchangeFunction(kExprI32AtomicCompareExchange, 2, 0x16);
  assertTraps(kTrapUnalignedAccess, () => wasmCmpExchange(11, 6, 5));

  // Building functions with bad alignment should fail to compile
  assertThrows(() => GetAtomicBinOpFunction(kExprI32AtomicSub16U, 3, 0),
      WebAssembly.CompileError);
})();

function CmpExchgLoop(opcode, alignment) {
  print("TestI64AtomicCompareExchangeLoop" + alignment);
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "imported_mem", 0, 2, "shared");
  builder.addFunction("main", makeSig([kWasmI32], []))
      .addLocals(kWasmI64, 2)
      .addBody([
        kExprLoop, kWasmStmt,
          kExprLocalGet, 0,
          kExprLocalGet, 1,
          kExprLocalGet, 2,
          kAtomicPrefix, opcode, alignment, 0,
          kExprLocalGet, 1,
          kExprI64Ne,
          kExprBrIf, 0,
          kExprEnd
      ])
      .exportFunc();
  let mem = new WebAssembly.Memory({initial: 2, maximum: 2, shared: true});
  let module = new WebAssembly.Module(builder.toBuffer());
  let instance = new WebAssembly.Instance(module, {m: {imported_mem: mem}});
}

(function TestAtomicCompareExchgLoop() {
  CmpExchgLoop(kExprI64AtomicCompareExchange, 3);
  CmpExchgLoop(kExprI64AtomicCompareExchange32U, 2);
  CmpExchgLoop(kExprI64AtomicCompareExchange16U, 1);
  CmpExchgLoop(kExprI64AtomicCompareExchange8U, 0);
})();
