text: // 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: --experimental-wasm-threads
  
  load("test/mjsunit/wasm/wasm-module-builder.js");
  
  const kSequenceLength = 8192;
  const kNumberOfWorkers = 4;
  const kBitMask = kNumberOfWorkers - 1;
  const kMemoryAddress = 0;
  const kSequenceStartAddress = 32;
  
  function makeWorkerCodeForOpcode(compareExchangeOpcode, size, functionName,
      builder) {
      let loadMemOpcode = kTrapUnreachable;
      switch (size) {
          case 64:
              loadMemOpcode = kExprI64LoadMem;
              break;
          case 32:
              loadMemOpcode = kExprI64LoadMem32U;
              break;
          case 16:
              loadMemOpcode = kExprI64LoadMem16U;
              break;
          case 8:
              loadMemOpcode = kExprI64LoadMem8U;
              break;
          default:
              throw "!";
      }
      const kArgMemoryCell = 0; // target for atomic ops
      const kArgSequencePtr = 1; // address of sequence
      const kArgSeqenceLength = 2; // lenght of sequence
      const kArgWorkerId = 3; // id of this worker
      const kArgBitMask = 4; // mask to extract worker id from value
      const kLocalCurrentOffset = 5; // current position in sequence in bytes
      const kLocalExpectedValue = 6; // the value we are waiting for
      const kLocalNextValue = 7; // the value to write in the update
      let body = [
          // Turn sequence length to equivalent in bytes.
          kExprLocalGet, kArgSeqenceLength,
          kExprI32Const, size / 8,
          kExprI32Mul,
          kExprLocalSet, kArgSeqenceLength,
          // Outer block so we have something to jump for return.
          ...[kExprBlock, kWasmStmt,
              // Set counter to 0.
              kExprI32Const, 0,
              kExprLocalSet, kLocalCurrentOffset,
              // Outer loop until maxcount.
              ...[kExprLoop, kWasmStmt,
                  // Find the next value to wait for.
                  ...[kExprLoop, kWasmStmt,
                      // Check end of sequence.
                      kExprLocalGet, kLocalCurrentOffset,
                      kExprLocalGet, kArgSeqenceLength,
                      kExprI32Eq,
                      kExprBrIf, 2, // return
                      ...[kExprBlock, kWasmStmt,
                          // Load next value.
                          kExprLocalGet, kArgSequencePtr,
                          kExprLocalGet, kLocalCurrentOffset,
                          kExprI32Add,
                          loadMemOpcode, 0, 0,
                          // Mask off bits.
                          kExprLocalGet, kArgBitMask,
                          kExprI64UConvertI32,
                          kExprI64And,
                          // Compare with worker id.
                          kExprLocalGet, kArgWorkerId,
                          kExprI64UConvertI32,
                          kExprI64Eq,
                          kExprBrIf, 0,
                          // Not found, increment position.
                          kExprLocalGet, kLocalCurrentOffset,
                          kExprI32Const, size / 8,
                          kExprI32Add,
                          kExprLocalSet, kLocalCurrentOffset,
                          kExprBr, 1,
                          kExprEnd
                      ],
                      // Found, end loop.
                      kExprEnd
                  ],
                  // Load expected value to local.
                  kExprLocalGet, kArgSequencePtr,
                  kExprLocalGet, kLocalCurrentOffset,
                  kExprI32Add,
                  loadMemOpcode, 0, 0,
                  kExprLocalSet, kLocalExpectedValue,
                  // Load value after expected one.
                  kExprLocalGet, kArgSequencePtr,
                  kExprLocalGet, kLocalCurrentOffset,
                  kExprI32Add,
                  kExprI32Const, size / 8,
                  kExprI32Add,
                  loadMemOpcode, 0, 0,
                  kExprLocalSet, kLocalNextValue,
                  // Hammer on memory until value found.
                  ...[kExprLoop, kWasmStmt,
                      // Load address.
                      kExprLocalGet, kArgMemoryCell,
                      // Load expected value.
                      kExprLocalGet, kLocalExpectedValue,
                      // Load updated value.
                      kExprLocalGet, kLocalNextValue,
                      // Try update.
                      kAtomicPrefix, compareExchangeOpcode, 0, 0,
                      // Load expected value.
                      kExprLocalGet, kLocalExpectedValue,
                      // Spin if not what expected.
                      kExprI64Ne,
                      kExprBrIf, 0,
                      kExprEnd
                  ],
                  // Next iteration of loop.
                  kExprLocalGet, kLocalCurrentOffset,
                  kExprI32Const, size / 8,
                  kExprI32Add,
                  kExprLocalSet, kLocalCurrentOffset,
                  kExprBr, 0,
                  kExprEnd
              ], // outer loop
              kExprEnd
          ], // the block
          kExprReturn
      ];
      builder.addFunction(functionName, makeSig([kWasmI32, kWasmI32, kWasmI32,
              kWasmI32, kWasmI32
          ], []))
          .addLocals(kWasmI32, 1).addLocals(kWasmI64, 2)
          .addBody(body)
          .exportAs(functionName);
  }
  
  function generateSequence(typedarray, start, count) {
      let end = count + start;
      for (let i = start; i < end; i++) {
          typedarray[i] = Math.floor(Math.random() * 256);
      }
  }
  
  function spawnWorker(module, memory, address, sequence) {
      let workers = [];
      for (let i = 0; i < kNumberOfWorkers; i++) {
          let worker = new Worker(
              `onmessage = function(msg) {
                  this.instance = new WebAssembly.Instance(msg.module,
                      {m: {imported_mem: msg.memory}});
                  instance.exports.worker(msg.address, msg.sequence, msg.sequenceLength, msg.workerId,
                      msg.bitMask);
                  postMessage({workerId: msg.workerId});
              }`,
              {type: 'string'}
          );
          workers.push(worker);
          worker.postMessage({
              module: module,
              memory: memory,
              address: address,
              sequence: sequence,
              sequenceLength: kSequenceLength,
              workerId: i,
              bitMask: kBitMask
          });
      }
      return workers;
  }
  
  function waitForWorkers(workers) {
      for (let worker of workers) {
          worker.getMessage();
          worker.terminate();
      }
  }
  
  function testOpcode(opcode, opcodeSize) {
      print("Testing I64AtomicCompareExchange" + opcodeSize);
      let builder = new WasmModuleBuilder();
      builder.addImportedMemory("m", "imported_mem", 0, 2, "shared");
  
      makeWorkerCodeForOpcode(opcode, opcodeSize, "worker", builder);
  
      let memory = new WebAssembly.Memory({
          initial: 2,
          maximum: 2,
          shared: true
      });
      let memoryView = new Uint8Array(memory.buffer);
      let numBytes = opcodeSize / 8;
      generateSequence(
          memoryView, kSequenceStartAddress, kSequenceLength * numBytes);
  
      // Write the first element of the sequence to memory, such that the workers
      // can start running as soon as they are spawned.
      memoryView.copyWithin(
          kMemoryAddress, kSequenceStartAddress,
          kSequenceStartAddress + numBytes);
  
      let module = new WebAssembly.Module(builder.toBuffer());
      let workers =
          spawnWorker(module, memory, kMemoryAddress, kSequenceStartAddress);
  
      waitForWorkers(workers);
  
      print("DONE");
  }
  
  testOpcode(kExprI64AtomicCompareExchange, 64);
  testOpcode(kExprI64AtomicCompareExchange32U, 32);
  testOpcode(kExprI64AtomicCompareExchange16U, 16);
  testOpcode(kExprI64AtomicCompareExchange8U, 8);
  