// Copyright 2023 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-multi-memory

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

const builder = new WasmModuleBuilder();
const mem0_idx = builder.addImportedMemory('imp', 'mem0', 0, 20, 'shared');
const mem1_idx = builder.addImportedMemory('imp', 'mem1', 0, 20, 'shared');
for (let mem_idx of [mem0_idx, mem1_idx]) {
  builder.addFunction(`notify${mem_idx}`, kSig_i_ii)
      .addBody([
        kExprLocalGet, 0,                                   // -
        kExprLocalGet, 1,                                   // -
        kAtomicPrefix, kExprAtomicNotify, 0x42, mem_idx, 0  // -
      ])
      .exportFunc();
  builder.addFunction(`wait${mem_idx}`, kSig_i_iii)
      .addBody([
        kExprLocalGet, 0,  // -
        kExprLocalGet, 1,  // -
        kExprLocalGet, 2,  // -
        // Convert to i64 and multiply by 1e6 (ms -> ns).
        kExprI64UConvertI32,                                 // -
        ...wasmI64Const(1e6),                                // -
        kExprI64Mul,                                         // -
        kAtomicPrefix, kExprI32AtomicWait, 0x42, mem_idx, 0  // -
      ])
      .exportFunc();
}
const module = builder.toModule();

const mem0 = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
const mem1 = new WebAssembly.Memory({initial: 2, maximum: 2, shared: true});

const mem0_value = 0;
const mem1_value = 1;
new Uint32Array(mem1.buffer).fill(mem1_value);

const imports = {
  imp: {mem0: mem0, mem1: mem1}
};
const instance = new WebAssembly.Instance(module, imports);
const {notify0, notify1, wait0, wait1} = instance.exports;

const k10Ms = 10;
const k10s = 10000;

(function TestWaitNotEqual() {
  print(arguments.callee.name);
  assertEquals(kAtomicWaitNotEqual, wait0(0, mem0_value + 1, k10Ms));
  assertEquals(kAtomicWaitNotEqual, wait1(0, mem1_value + 1, k10Ms));
})();

(function TestWaitTimeout() {
  print(arguments.callee.name);
  assertEquals(kAtomicWaitTimedOut, wait0(0, mem0_value, k10Ms));
  assertEquals(kAtomicWaitTimedOut, wait1(0, mem1_value, k10Ms));
})();

(function TestWakeUpWorker() {
  print(arguments.callee.name);
  function workerCode() {
    instance = undefined;
    onmessage = function(msg) {
      if (!instance) {
        instance = new WebAssembly.Instance(msg.module, msg.imports);
        postMessage('instantiated');
        return;
      }
      if (msg.action === 'wait0' || msg.action === 'wait1') {
        let result = instance.exports[msg.action](...msg.arguments);
        print(`[worker] ${msg.action} ->: ${result}`);
        postMessage(result);
        return;
      }
      postMessage(`Invalid action: ${msg.action}`);
    }
  }
  let worker = new Worker(workerCode, {type: 'function'});

  worker.postMessage({module: module, imports: imports});
  assertEquals('instantiated', worker.getMessage());

  const offset = 48;
  for (let [mem_idx, mem_value] of [
           [mem0_idx, mem0_value], [mem1_idx, mem1_value]]) {
    print(`- memory ${mem_idx}`);

    // Test "not equals".
    worker.postMessage(
        {action: `wait${mem_idx}`, arguments: [offset, mem_value + 1, k10Ms]});
    assertEquals(kAtomicWaitNotEqual, worker.getMessage());

    // Test "timed out".
    worker.postMessage(
        {action: `wait${mem_idx}`, arguments: [offset, mem_value, k10Ms]});
    assertEquals(kAtomicWaitTimedOut, worker.getMessage());

    // Test "ok".
    worker.postMessage(
        {action: `wait${mem_idx}`, arguments: [offset, mem_value, k10s]});
    const started = performance.now();
    let notify = mem_idx == 0 ? notify0 : notify1;
    let notified;
    while ((notified = notify(offset, 1)) === 0) {
      const now = performance.now();
      if (now - started > k10s) {
        throw new Error('Could not notify worker within 10s');
      }
    }
    assertEquals(1, notified);
    assertEquals(kAtomicWaitOk, worker.getMessage());
  }

  worker.terminate();
})();
