/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/AtomicOperations.h"
#include "jsapi-tests/tests.h"
#include "vm/ArrayBufferObject.h"
#include "vm/SharedMem.h"
#include "vm/Uint8Clamped.h"
#include "wasm/WasmFeatures.h"

using namespace js;

// Machinery to disguise pointer addresses to the C++ compiler -- quite possibly
// not thread-safe.

extern void setHiddenPointer(void* p);
extern void* getHiddenPointer();

void* hidePointerValue(void* p) {
  setHiddenPointer(p);
  return getHiddenPointer();
}

//////////////////////////////////////////////////////////////////////
//
// Lock-freedom predicates

BEGIN_REUSABLE_TEST(testAtomicLockFree8) {
  // isLockfree8() must not return true if there are no 8-byte atomics

  CHECK(!jit::AtomicOperations::isLockfree8() ||
        jit::AtomicOperations::hasAtomic8());

  // We must have lock-free 8-byte atomics on every platform where we support
  // wasm, but we don't care otherwise.

  CHECK(!wasm::HasSupport(cx) || jit::AtomicOperations::isLockfree8());
  return true;
}
END_TEST(testAtomicLockFree8)

// The JS spec requires specific behavior for all but 1 and 2.

BEGIN_REUSABLE_TEST(testAtomicLockFreeJS) {
  static_assert(jit::AtomicOperations::isLockfreeJS(1) ==
                true);  // false is allowed by spec but not in SpiderMonkey
  static_assert(jit::AtomicOperations::isLockfreeJS(2) == true);   // ditto
  static_assert(jit::AtomicOperations::isLockfreeJS(8) == true);   // ditto
  static_assert(jit::AtomicOperations::isLockfreeJS(3) == false);  // required
  static_assert(jit::AtomicOperations::isLockfreeJS(4) == true);   // required
  static_assert(jit::AtomicOperations::isLockfreeJS(5) == false);  // required
  static_assert(jit::AtomicOperations::isLockfreeJS(6) == false);  // required
  static_assert(jit::AtomicOperations::isLockfreeJS(7) == false);  // required
  return true;
}
END_TEST(testAtomicLockFreeJS)

//////////////////////////////////////////////////////////////////////
//
// Fence

// This only tests that fenceSeqCst is defined and that it doesn't crash if we
// call it, but it has no return value and its effect is not observable here.

BEGIN_REUSABLE_TEST(testAtomicFence) {
  jit::AtomicOperations::fenceSeqCst();
  return true;
}
END_TEST(testAtomicFence)

//////////////////////////////////////////////////////////////////////
//
// Memory access primitives

// These tests for the atomic load and store primitives ascertain that the
// primitives are defined and that they load and store the values they should,
// but not that the primitives are actually atomic wrt to the memory subsystem.

// Memory for testing atomics.  This must be aligned to the natural alignment of
// the type we're testing; for now, use 8-byte alignment for all.

alignas(8) static uint8_t atomicMem[8];
alignas(8) static uint8_t atomicMem2[8];

// T is the primitive type we're testing, and A and B are references to constant
// bindings holding values of that type.
//
// No bytes of A and B should be 0 or FF.  A+B and A-B must not overflow.

#define ATOMIC_TESTS(T, A, B)                                           \
  T* q = (T*)hidePointerValue((void*)atomicMem);                        \
  *q = A;                                                               \
  SharedMem<T*> p =                                                     \
      SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem));       \
  CHECK(*q == A);                                                       \
  CHECK(jit::AtomicOperations::loadSeqCst(p) == A);                     \
  CHECK(*q == A);                                                       \
  jit::AtomicOperations::storeSeqCst(p, B);                             \
  CHECK(*q == B);                                                       \
  CHECK(jit::AtomicOperations::exchangeSeqCst(p, A) == B);              \
  CHECK(*q == A);                                                       \
  CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, (T)0, (T)1) ==  \
        A); /*failure*/                                                 \
  CHECK(*q == A);                                                       \
  CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, A, B) ==        \
        A); /*success*/                                                 \
  CHECK(*q == B);                                                       \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::fetchAddSeqCst(p, B) == A);              \
  CHECK(*q == A + B);                                                   \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::fetchSubSeqCst(p, B) == A);              \
  CHECK(*q == A - B);                                                   \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::fetchAndSeqCst(p, B) == A);              \
  CHECK(*q == (A & B));                                                 \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::fetchOrSeqCst(p, B) == A);               \
  CHECK(*q == (A | B));                                                 \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::fetchXorSeqCst(p, B) == A);              \
  CHECK(*q == (A ^ B));                                                 \
  *q = A;                                                               \
  CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A);               \
  jit::AtomicOperations::storeSafeWhenRacy(p, B);                       \
  CHECK(*q == B);                                                       \
  T* q2 = (T*)hidePointerValue((void*)atomicMem2);                      \
  SharedMem<T*> p2 =                                                    \
      SharedMem<T*>::shared((T*)hidePointerValue((void*)atomicMem2));   \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T));          \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T));         \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1);                 \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1);                 \
  CHECK(*q2 == A);                                                      \
  return true

BEGIN_REUSABLE_TEST(testAtomicOperationsU8) {
  const uint8_t A = 0xab;
  const uint8_t B = 0x37;
  ATOMIC_TESTS(uint8_t, A, B);
}
END_TEST(testAtomicOperationsU8)

BEGIN_REUSABLE_TEST(testAtomicOperationsI8) {
  const int8_t A = 0x3b;
  const int8_t B = 0x27;
  ATOMIC_TESTS(int8_t, A, B);
}
END_TEST(testAtomicOperationsI8)

BEGIN_REUSABLE_TEST(testAtomicOperationsU16) {
  const uint16_t A = 0xabdc;
  const uint16_t B = 0x3789;
  ATOMIC_TESTS(uint16_t, A, B);
}
END_TEST(testAtomicOperationsU16)

BEGIN_REUSABLE_TEST(testAtomicOperationsI16) {
  const int16_t A = 0x3bdc;
  const int16_t B = 0x2737;
  ATOMIC_TESTS(int16_t, A, B);
}
END_TEST(testAtomicOperationsI16)

BEGIN_REUSABLE_TEST(testAtomicOperationsU32) {
  const uint32_t A = 0xabdc0588;
  const uint32_t B = 0x37891942;
  ATOMIC_TESTS(uint32_t, A, B);
}
END_TEST(testAtomicOperationsU32)

BEGIN_REUSABLE_TEST(testAtomicOperationsI32) {
  const int32_t A = 0x3bdc0588;
  const int32_t B = 0x27371843;
  ATOMIC_TESTS(int32_t, A, B);
}
END_TEST(testAtomicOperationsI32)

BEGIN_REUSABLE_TEST(testAtomicOperationsU64) {
  if (!jit::AtomicOperations::hasAtomic8()) {
    return true;
  }

  const uint64_t A(0x9aadf00ddeadbeef);
  const uint64_t B(0x4eedbead1337f001);
  ATOMIC_TESTS(uint64_t, A, B);
}
END_TEST(testAtomicOperationsU64)

BEGIN_REUSABLE_TEST(testAtomicOperationsI64) {
  if (!jit::AtomicOperations::hasAtomic8()) {
    return true;
  }

  const int64_t A(0x2aadf00ddeadbeef);
  const int64_t B(0x4eedbead1337f001);
  ATOMIC_TESTS(int64_t, A, B);
}
END_TEST(testAtomicOperationsI64)

// T is the primitive float type we're testing, and A and B are references to
// constant bindings holding values of that type.
//
// Stay away from 0, NaN, infinities, and denormals.

#define ATOMIC_FLOAT_TESTS(T, A, B)                                     \
  T* q = (T*)hidePointerValue((void*)atomicMem);                        \
  *q = A;                                                               \
  SharedMem<T*> p =                                                     \
      SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem));       \
  CHECK(*q == A);                                                       \
  CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A);               \
  jit::AtomicOperations::storeSafeWhenRacy(p, B);                       \
  CHECK(*q == B);                                                       \
  T* q2 = (T*)hidePointerValue((void*)atomicMem2);                      \
  SharedMem<T*> p2 =                                                    \
      SharedMem<T*>::shared((T*)hidePointerValue((void*)atomicMem2));   \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T));          \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T));         \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1);                 \
  CHECK(*q2 == A);                                                      \
  *q = A;                                                               \
  *q2 = B;                                                              \
  jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1);                 \
  CHECK(*q2 == A);                                                      \
  return true

BEGIN_REUSABLE_TEST(testAtomicOperationsF32) {
  const float A(123.25);
  const float B(-987.75);
  ATOMIC_FLOAT_TESTS(float, A, B);
}
END_TEST(testAtomicOperationsF32)

BEGIN_REUSABLE_TEST(testAtomicOperationsF64) {
  const double A(123.25);
  const double B(-987.75);
  ATOMIC_FLOAT_TESTS(double, A, B);
}
END_TEST(testAtomicOperationsF64)

#define ATOMIC_CLAMPED_TESTS(T, A, B)                             \
  T* q = (T*)hidePointerValue((void*)atomicMem);                  \
  *q = A;                                                         \
  SharedMem<T*> p =                                               \
      SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem)); \
  CHECK(*q == A);                                                 \
  CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A);         \
  jit::AtomicOperations::storeSafeWhenRacy(p, B);                 \
  CHECK(*q == B);                                                 \
  return true

BEGIN_REUSABLE_TEST(testAtomicOperationsU8Clamped) {
  const uint8_clamped A(0xab);
  const uint8_clamped B(0x37);
  ATOMIC_CLAMPED_TESTS(uint8_clamped, A, B);
}
END_TEST(testAtomicOperationsU8Clamped)

#undef ATOMIC_TESTS
#undef ATOMIC_FLOAT_TESTS
#undef ATOMIC_CLAMPED_TESTS
