// Copyright 2018 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: --allow-natives-syntax --harmony-sharedarraybuffer
// Flags: --experimental-wasm-threads

'use strict';

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

function WasmAtomicNotify(memory, offset, index, num) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "memory", 0, 20, "shared");
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kAtomicPrefix,
      kExprAtomicNotify, /* alignment */ 0, offset])
    .exportAs("main");

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

function WasmI32AtomicWait(memory, offset, index, val, timeout) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "memory", 0, 20, "shared");
  builder.addFunction("main",
    makeSig([kWasmI32, kWasmI32, kWasmF64], [kWasmI32]))
    .addBody([
      kExprLocalGet, 0,
      kExprLocalGet, 1,
      kExprLocalGet, 2,
      kExprI64SConvertF64,
      kAtomicPrefix,
      kExprI32AtomicWait, /* alignment */ 0, offset])
      .exportAs("main");

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

function WasmI64AtomicWait(memory, offset, index, val_low,
                                   val_high, timeout) {
  let builder = new WasmModuleBuilder();
  builder.addImportedMemory("m", "memory", 0, 20, "shared");
  // Wrapper for I64AtomicWait that takes two I32 values and combines to into
  // I64 for the instruction parameter.
  builder.addFunction("main",
    makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmF64], [kWasmI32]))
    .addLocals(kWasmI64, 1) // local that is passed as value param to wait
    .addBody([
      kExprLocalGet, 1,
      kExprI64UConvertI32,
      kExprI64Const, 32,
      kExprI64Shl,
      kExprLocalGet, 2,
      kExprI64UConvertI32,
      kExprI64Ior,
      kExprLocalSet, 4, // Store the created I64 value in local
      kExprLocalGet, 0,
      kExprLocalGet, 4,
      kExprLocalGet, 3,
      kExprI64SConvertF64,
      kAtomicPrefix,
      kExprI64AtomicWait, /* alignment */ 0, offset])
      .exportAs("main");

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

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

  // Valid indexes are 0-65535 (1 page).
  [-2, 65536, 0xffffffff].forEach(function(invalidIndex) {
    assertThrows(function() {
      WasmAtomicNotify(memory, 0, invalidIndex, -1);
    }, Error);
    assertThrows(function() {
      WasmI32AtomicWait(memory, 0, invalidIndex, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, 0, invalidIndex, 0, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmAtomicNotify(memory, invalidIndex, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmI32AtomicWait(memory, invalidIndex, 0, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, invalidIndex, 0, 0, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmAtomicNotify(memory, invalidIndex/2, invalidIndex/2, -1);
    }, Error);
    assertThrows(function() {
      WasmI32AtomicWait(memory, invalidIndex/2, invalidIndex/2, 0, -1);
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, invalidIndex/2, invalidIndex/2, 0, 0, -1);
    }, Error);
  });
})();

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

  // Wait and wake must be 4 byte aligned.
  [1, 2, 3].forEach(function(invalid) {
    assertThrows(function() {
      WasmAtomicNotify(memory, invalid, 0, -1)
    }, Error);
    assertThrows(function() {
      WasmAtomicNotify(memory, 0, invalid, -1)
    }, Error);
    assertThrows(function() {
      WasmI32AtomicWait(memory, invalid, 0, 0, -1)
    }, Error);
    assertThrows(function() {
      WasmI32AtomicWait(memory, 0, invalid, 0, -1)
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, invalid, 0, 0, 0, -1)
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, 0, invalid, 0, 0, -1)
    }, Error);
  });

  //WasmI64AtomicWait must be 8 byte aligned.
  [4, 5, 6, 7].forEach(function(invalid) {
    assertThrows(function() {
      WasmI64AtomicWait(memory, 0, invalid, 0, 0, -1)
    }, Error);
    assertThrows(function() {
      WasmI64AtomicWait(memory, invalid, 0, 0, 0, -1)
    }, Error);
  });
})();

(function TestI32WaitTimeout() {
  let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
  var waitMs = 100;
  var startTime = new Date();
  assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 0, waitMs*1000000));
  var endTime = new Date();
  assertTrue(endTime - startTime >= waitMs);
})();

(function TestI64WaitTimeout() {
  let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
  var waitMs = 100;
  var startTime = new Date();
  assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 0, 0, waitMs*1000000));
  var endTime = new Date();
  assertTrue(endTime - startTime >= waitMs);
})();

(function TestI32WaitNotEqual() {
  let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
  assertEquals(1, WasmI32AtomicWait(memory, 0, 0, 42, -1));

  assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 0, 0));

  let i32a = new Int32Array(memory.buffer);
  i32a[0] = 1;
  assertEquals(1, WasmI32AtomicWait(memory, 0, 0, 0, -1));
  assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 1, 0));
})();

(function TestI64WaitNotEqual() {
  let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
  assertEquals(1, WasmI64AtomicWait(memory, 0, 0, 42, 0, -1));

  assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 0, 0, 0));

  let i32a = new Int32Array(memory.buffer);
  i32a[0] = 1;
  i32a[1] = 2;
  assertEquals(1, WasmI64AtomicWait(memory, 0, 0, 0, 0, -1));
  assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 1, 2, 0));
})();

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

  [-1, 0, 4, 100, 0xffffffff].forEach(function(count) {
    WasmAtomicNotify(memory, 0, 0, count);
  });
})();

//// WORKER ONLY TESTS

if (this.Worker) {

  // This test creates 4 workers that wait on consecutive (8 byte separated to
  // satisfy alignments for all kinds of wait) memory locations to test various
  // wait/wake combinations. For each combination, each thread waits 3 times
  // expecting all 4 threads to be woken with wake(4) in first iteration, all 4
  // to be woken with wake(5) in second iteration and, 3 and 1 to be woken in
  // third iteration.

  let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
  let i32a = new Int32Array(memory.buffer);
  const numWorkers = 4;

  let workerScript = `onmessage = function(msg) {
    load("test/mjsunit/wasm/wasm-module-builder.js");
    ${WasmI32AtomicWait.toString()}
    ${WasmI64AtomicWait.toString()}
    let id = msg.id;
    let memory = msg.memory;
    let i32a = new Int32Array(memory.buffer);
    // indices are right shifted by 2 for Atomics.wait to convert them to index
    // for Int32Array
    // for wasm-wake numWorkers threads
    let result = Atomics.wait(i32a, 0>>>2, 0);
    postMessage(result);
    // for wasm-wake numWorkers + 1 threads
    result = Atomics.wait(i32a, 8>>>2, 0);
    postMessage(result);
    // for wasm-wake numWorkers - 1 threads
    result = Atomics.wait(i32a, 16>>>2, 0);
    postMessage(result);
    // for js-wake numWorkers threads
    result = WasmI32AtomicWait(memory, 0, 24, 0, -1);
    postMessage(result);
    // for js-wake numWorkers + 1 threads
    result = WasmI32AtomicWait(memory, 0, 32, 0, -1);
    postMessage(result);
    // for js-wake numWorkers - 1 threads
    result = WasmI32AtomicWait(memory, 0, 40, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers threads
    result = WasmI32AtomicWait(memory, 0, 48, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers + 1 threads
    result = WasmI32AtomicWait(memory, 0, 56, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers - 1 threads
    result = WasmI32AtomicWait(memory, 0, 64, 0, -1);
    postMessage(result);
    // for js-wake numWorkers threads
    result = WasmI64AtomicWait(memory, 0, 72, 0, 0, -1);
    postMessage(result);
    // for js-wake numWorkers + 1 threads
    result = WasmI64AtomicWait(memory, 0, 80, 0, 0, -1);
    postMessage(result);
    // for js-wake numWorkers - 1 threads
    result = WasmI64AtomicWait(memory, 0, 88, 0, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers threads
    result = WasmI64AtomicWait(memory, 0, 96, 0, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers + 1 threads
    result = WasmI64AtomicWait(memory, 0, 104, 0, 0, -1);
    postMessage(result);
    // for wasm-wake numWorkers - 1 threads
    result = WasmI64AtomicWait(memory, 0, 112, 0, 0, -1);
    postMessage(result);
  };`;

  let waitForAllWorkers = function(index) {
    // index is right shifted by 2 to convert to index in Int32Array
    while (%AtomicsNumWaitersForTesting(i32a, index>>>2) != numWorkers) {}
  }

  let jsWakeCheck = function(index, num, workers, msg) {
    waitForAllWorkers(index);
    let indexJs = index>>>2; // convert to index in Int32Array
    if (num >= numWorkers) {
      // if numWorkers or more is passed to wake, numWorkers workers should be
      // woken.
      assertEquals(numWorkers, Atomics.notify(i32a, indexJs, num));
    } else {
      // if num < numWorkers is passed to wake, num workers should be woken.
      // Then the remaining workers are woken for the next part
      assertEquals(num, Atomics.notify(i32a, indexJs, num));
      assertEquals(numWorkers-num, Atomics.notify(i32a, indexJs, numWorkers));
    }
    for (let id = 0; id < numWorkers; id++) {
      assertEquals(msg, workers[id].getMessage());
    }
  };

  let wasmWakeCheck = function(index, num, workers, msg) {
    waitForAllWorkers(index);
    if (num >= numWorkers) {
      // if numWorkers or more is passed to wake, numWorkers workers should be
      // woken.
      assertEquals(numWorkers, WasmAtomicNotify(memory, 0, index, num));
    } else {
      // if num < numWorkers is passed to wake, num workers should be woken.
      // Then the remaining workers are woken for the next part
      assertEquals(num, WasmAtomicNotify(memory, 0, index, num));
      assertEquals(numWorkers-num,
                   WasmAtomicNotify(memory, 0, index, numWorkers));
    }
    for (let id = 0; id < numWorkers; id++) {
      assertEquals(msg, workers[id].getMessage());
    }
  };

  let workers = [];
  for (let id = 0; id < numWorkers; id++) {
    workers[id] = new Worker(workerScript, {type: 'string'});
    workers[id].postMessage({id, memory});
  }

  wasmWakeCheck(0, numWorkers, workers, "ok");
  wasmWakeCheck(8, numWorkers + 1, workers, "ok");
  wasmWakeCheck(16, numWorkers - 1, workers, "ok");

  jsWakeCheck(24, numWorkers, workers, 0);
  jsWakeCheck(32, numWorkers + 1, workers, 0);
  jsWakeCheck(40, numWorkers - 1, workers, 0);

  wasmWakeCheck(48, numWorkers, workers, 0);
  wasmWakeCheck(56, numWorkers + 1, workers, 0);
  wasmWakeCheck(64, numWorkers - 1, workers, 0);

  jsWakeCheck(72, numWorkers, workers, 0);
  jsWakeCheck(80, numWorkers + 1, workers, 0);
  jsWakeCheck(88, numWorkers - 1, workers, 0);

  wasmWakeCheck(96, numWorkers, workers, 0);
  wasmWakeCheck(104, numWorkers + 1, workers, 0);
  wasmWakeCheck(112, numWorkers - 1, workers, 0);

  for (let id = 0; id < numWorkers; id++) {
    workers[id].terminate();
  }
}
