/*
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "cppstdlib/type_traits.hpp"
#include "metaprogramming/primitiveConversions.hpp"
#include "runtime/atomic.hpp"

#include "unittest.hpp"

// These tests of Atomic<T> only verify functionality.  They don't verify
// atomicity.

template<typename T>
struct AtomicIntegerArithmeticTestSupport {
  Atomic<T> _test_value;

  static constexpr T _old_value =    static_cast<T>(UCONST64(0x2000000020000));
  static constexpr T _change_value = static_cast<T>(UCONST64(    0x100000001));

  AtomicIntegerArithmeticTestSupport() : _test_value(0) {}

  void fetch_then_add() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value + _change_value;
    T result = _test_value.fetch_then_add(_change_value);
    EXPECT_EQ(_old_value, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void fetch_then_sub() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value - _change_value;
    T result = _test_value.fetch_then_sub(_change_value);
    EXPECT_EQ(_old_value, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void add_then_fetch() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value + _change_value;
    T result = _test_value.add_then_fetch(_change_value);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void sub_then_fetch() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value - _change_value;
    T result = _test_value.sub_then_fetch(_change_value);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

#define TEST_ARITHMETIC(name) { SCOPED_TRACE(XSTR(name)); name(); }

  void operator()() {
    TEST_ARITHMETIC(fetch_then_add)
    TEST_ARITHMETIC(fetch_then_sub)
    TEST_ARITHMETIC(add_then_fetch)
    TEST_ARITHMETIC(sub_then_fetch)
  }

#undef TEST_ARITHMETIC
};

TEST_VM(AtomicIntegerTest, arith_int32) {
  AtomicIntegerArithmeticTestSupport<int32_t>()();
}

TEST_VM(AtomicIntegerTest, arith_uint32) {
  AtomicIntegerArithmeticTestSupport<uint32_t>()();
}

TEST_VM(AtomicIntegerTest, arith_int64) {
  AtomicIntegerArithmeticTestSupport<int64_t>()();
}

TEST_VM(AtomicIntegerTest, arith_uint64) {
  AtomicIntegerArithmeticTestSupport<uint64_t>()();
}

template<typename T>
struct AtomicByteAndIntegerXchgTestSupport {
  Atomic<T> _test_value;

  AtomicByteAndIntegerXchgTestSupport() : _test_value{} {}

  void test() {
    T zero = 0;
    T five = 5;
    _test_value.store_relaxed(zero);
    T res = _test_value.exchange(five);
    EXPECT_EQ(zero, res);
    EXPECT_EQ(five, _test_value.load_relaxed());
  }
};

TEST_VM(AtomicIntegerTest, xchg_char) {
  using Support = AtomicByteAndIntegerXchgTestSupport<char>;
  Support().test();
}

TEST_VM(AtomicIntegerTest, xchg_int32) {
  using Support = AtomicByteAndIntegerXchgTestSupport<int32_t>;
  Support().test();
}

TEST_VM(AtomicIntegerTest, xchg_int64) {
  using Support = AtomicByteAndIntegerXchgTestSupport<int64_t>;
  Support().test();
}

template<typename T>
struct AtomicIntegerCmpxchgTestSupport {
  Atomic<T> _test_value;

  AtomicIntegerCmpxchgTestSupport() : _test_value{} {}

  void test() {
    T zero = 0;
    T five = 5;
    T ten = 10;
    _test_value.store_relaxed(zero);
    T res = _test_value.compare_exchange(five, ten);
    EXPECT_EQ(zero, res);
    EXPECT_EQ(zero, _test_value.load_relaxed());
    res = _test_value.compare_exchange(zero, ten);
    EXPECT_EQ(zero, res);
    EXPECT_EQ(ten, _test_value.load_relaxed());
  }
};

TEST_VM(AtomicIntegerTest, cmpxchg_int32) {
  using Support = AtomicIntegerCmpxchgTestSupport<int32_t>;
  Support().test();
}

TEST_VM(AtomicIntegerTest, cmpxchg_int64) {
  // Check if 64-bit atomics are available on the machine.
  using Support = AtomicIntegerCmpxchgTestSupport<int64_t>;
  Support().test();
}

template<typename T>
struct AtomicIntegerCmpsetTestSupport {
  Atomic<T> _test_value;

  AtomicIntegerCmpsetTestSupport() : _test_value{} {}

  void test() {
    T zero = 0;
    T five = 5;
    T ten = 10;
    _test_value.store_relaxed(zero);
    EXPECT_FALSE(_test_value.compare_set(five, ten));
    EXPECT_EQ(zero, _test_value.load_relaxed());
    EXPECT_TRUE(_test_value.compare_set(zero, ten));
    EXPECT_EQ(ten, _test_value.load_relaxed());
  }
};

TEST_VM(AtomicIntegerTest, cmpset_int32) {
  using Support = AtomicIntegerCmpsetTestSupport<int32_t>;
  Support().test();
}

TEST_VM(AtomicIntegerTest, cmpset_int64) {
  // Check if 64-bit atomics are available on the machine.
  using Support = AtomicIntegerCmpsetTestSupport<int64_t>;
  Support().test();
}

struct AtomicXchgAndCmpxchg1ByteStressSupport {
  char _default_val;
  int  _base;
  Atomic<char> _array[7+32+7];

  AtomicXchgAndCmpxchg1ByteStressSupport() : _default_val(0x7a), _base(7) {}

  void validate(char val, char val2, int index) {
    for (int i = 0; i < 7; i++) {
      EXPECT_EQ(_array[i].load_relaxed(), _default_val);
    }
    for (int i = 7; i < (7+32); i++) {
      if (i == index) {
        EXPECT_EQ(_array[i].load_relaxed(), val2);
      } else {
        EXPECT_EQ(_array[i].load_relaxed(), val);
      }
    }
    for (int i = 0; i < 7; i++) {
      EXPECT_EQ(_array[i].load_relaxed(), _default_val);
    }
  }

  template <typename Exchange>
  void test_index(int index) {
    Exchange exchange;
    char one = 1;
    exchange(_array[index], _default_val, one);
    validate(_default_val, one, index);

    exchange(_array[index], one, _default_val);
    validate(_default_val, _default_val, index);
  }

  template <typename Exchange>
  void test() {
    for (size_t i = 0; i < ARRAY_SIZE(_array); ++i) {
      _array[i].store_relaxed(_default_val);
    }
    for (int i = _base; i < (_base+32); i++) {
      test_index<Exchange>(i);
    }
  }
  void test_exchange() {
    struct StressWithExchange {
      void operator()(Atomic<char>& atomic, char compare_value, char new_value) {
        EXPECT_EQ(compare_value, atomic.exchange(new_value));
      }
    };
    test<StressWithExchange>();
  }

  void test_compare_exchange() {
    struct StressWithCompareExchange {
      void operator()(Atomic<char>& atomic, char compare_value, char new_value) {
        EXPECT_EQ(compare_value, atomic.compare_exchange(compare_value, new_value));
      }
    };
    test<StressWithCompareExchange>();
  }
};

TEST_VM(AtomicByteTest, stress_xchg) {
  AtomicXchgAndCmpxchg1ByteStressSupport support;
  support.test_exchange();
}

TEST_VM(AtomicByteTest, stress_cmpxchg) {
  AtomicXchgAndCmpxchg1ByteStressSupport support;
  support.test_compare_exchange();
}

template<typename T>
struct AtomicTestSupport {
  Atomic<T> _test_value;

  AtomicTestSupport() : _test_value{} {}

  void test_store_load(T value) {
    EXPECT_NE(value, _test_value.load_relaxed());
    _test_value.store_relaxed(value);
    EXPECT_EQ(value, _test_value.load_relaxed());
  }

  void test_cmpxchg(T value1, T value2) {
    EXPECT_NE(value1, _test_value.load_relaxed());
    _test_value.store_relaxed(value1);
    EXPECT_EQ(value1, _test_value.compare_exchange(value2, value2));
    EXPECT_EQ(value1, _test_value.load_relaxed());
    EXPECT_EQ(value1, _test_value.compare_exchange(value1, value2));
    EXPECT_EQ(value2, _test_value.load_relaxed());
  }

  void test_xchg(T value1, T value2) {
    EXPECT_NE(value1, _test_value.load_relaxed());
    _test_value.store_relaxed(value1);
    EXPECT_EQ(value1, _test_value.exchange(value2));
    EXPECT_EQ(value2, _test_value.load_relaxed());
  }

  template <T B, T C>
  static void test() {
    AtomicTestSupport().test_store_load(B);
    AtomicTestSupport().test_cmpxchg(B, C);
    AtomicTestSupport().test_xchg(B, C);
  }
};

namespace AtomicEnumTestUnscoped {       // Scope the enumerators.
  enum TestEnum { A, B, C };
}

TEST_VM(AtomicEnumTest, unscoped_enum) {
  using namespace AtomicEnumTestUnscoped;
  AtomicTestSupport<TestEnum>::test<B, C>();
}

enum class AtomicEnumTestScoped { A, B, C };

TEST_VM(AtomicEnumTest, scoped_enum) {
  const AtomicEnumTestScoped B = AtomicEnumTestScoped::B;
  const AtomicEnumTestScoped C = AtomicEnumTestScoped::C;
  AtomicTestSupport<AtomicEnumTestScoped>::test<B, C>();
}

enum class AtomicEnumTestScoped64Bit : uint64_t { A, B, C };

TEST_VM(AtomicEnumTest, scoped_enum_64_bit) {
  const AtomicEnumTestScoped64Bit B = AtomicEnumTestScoped64Bit::B;
  const AtomicEnumTestScoped64Bit C = AtomicEnumTestScoped64Bit::C;
  AtomicTestSupport<AtomicEnumTestScoped64Bit>::test<B, C>();
}

enum class AtomicEnumTestScoped8Bit : uint8_t { A, B, C };

TEST_VM(AtomicEnumTest, scoped_enum_8_bit) {
  const AtomicEnumTestScoped8Bit B = AtomicEnumTestScoped8Bit::B;
  const AtomicEnumTestScoped8Bit C = AtomicEnumTestScoped8Bit::C;
  AtomicTestSupport<AtomicEnumTestScoped8Bit>::test<B, C>();
}

TEST_VM(AtomicByteTest, char_test) {
  const char B = 0xB;
  const char C = 0xC;
  AtomicTestSupport<char>::test<B, C>();
}

TEST_VM(AtomicByteTest, bool_test) {
  const bool B = true;
  const bool C = false;
  AtomicTestSupport<bool>::test<B, C>();
}

template<typename T>
struct AtomicBitopsTestSupport {
  Atomic<T> _test_value;

  // At least one byte differs between _old_value and _old_value op _change_value.
  static constexpr T _old_value =    static_cast<T>(UCONST64(0x7f5300007f530044));
  static constexpr T _change_value = static_cast<T>(UCONST64(0x3800530038005322));

  AtomicBitopsTestSupport() : _test_value(0) {}

  void fetch_then_and() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value & _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.fetch_then_and(_change_value);
    EXPECT_EQ(_old_value, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void fetch_then_or() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value | _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.fetch_then_or(_change_value);
    EXPECT_EQ(_old_value, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void fetch_then_xor() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value ^ _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.fetch_then_xor(_change_value);
    EXPECT_EQ(_old_value, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void and_then_fetch() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value & _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.and_then_fetch(_change_value);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void or_then_fetch() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value | _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.or_then_fetch(_change_value);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void xor_then_fetch() {
    _test_value.store_relaxed(_old_value);
    T expected = _old_value ^ _change_value;
    EXPECT_NE(_old_value, expected);
    T result = _test_value.xor_then_fetch(_change_value);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

#define TEST_BITOP(name) { SCOPED_TRACE(XSTR(name)); name(); }

  void operator()() {
    TEST_BITOP(fetch_then_and)
    TEST_BITOP(fetch_then_or)
    TEST_BITOP(fetch_then_xor)
    TEST_BITOP(and_then_fetch)
    TEST_BITOP(or_then_fetch)
    TEST_BITOP(xor_then_fetch)
  }

#undef TEST_BITOP
};

TEST_VM(AtomicBitopsTest, int32) {
  AtomicBitopsTestSupport<int32_t>()();
}

TEST_VM(AtomicBitopsTest, uint32) {
  AtomicBitopsTestSupport<uint32_t>()();
}

TEST_VM(AtomicBitopsTest, int64) {
  AtomicBitopsTestSupport<int64_t>()();
}

TEST_VM(AtomicBitopsTest, uint64) {
  AtomicBitopsTestSupport<uint64_t>()();
}

template<typename T>
struct AtomicPointerTestSupport {
  static T _test_values[10];
  static T* _initial_ptr;

  Atomic<T*> _test_value;

  AtomicPointerTestSupport() : _test_value(nullptr) {}

  void fetch_then_add() {
    _test_value.store_relaxed(_initial_ptr);
    T* expected = _initial_ptr + 2;
    T* result = _test_value.fetch_then_add(2);
    EXPECT_EQ(_initial_ptr, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void fetch_then_sub() {
    _test_value.store_relaxed(_initial_ptr);
    T* expected = _initial_ptr - 2;
    T* result = _test_value.fetch_then_sub(2);
    EXPECT_EQ(_initial_ptr, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void add_then_fetch() {
    _test_value.store_relaxed(_initial_ptr);
    T* expected = _initial_ptr + 2;
    T* result = _test_value.add_then_fetch(2);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void sub_then_fetch() {
    _test_value.store_relaxed(_initial_ptr);
    T* expected = _initial_ptr - 2;
    T* result = _test_value.sub_then_fetch(2);
    EXPECT_EQ(expected, result);
    EXPECT_EQ(expected, _test_value.load_relaxed());
  }

  void exchange() {
    _test_value.store_relaxed(_initial_ptr);
    T* replace = _initial_ptr + 3;
    T* result = _test_value.exchange(replace);
    EXPECT_EQ(_initial_ptr, result);
    EXPECT_EQ(replace, _test_value.load_relaxed());
  }

  void compare_exchange() {
    _test_value.store_relaxed(_initial_ptr);
    T* not_initial_ptr = _initial_ptr - 1;
    T* replace = _initial_ptr + 3;

    T* result = _test_value.compare_exchange(not_initial_ptr, replace);
    EXPECT_EQ(_initial_ptr, result);
    EXPECT_EQ(_initial_ptr, _test_value.load_relaxed());

    result = _test_value.compare_exchange(_initial_ptr, replace);
    EXPECT_EQ(_initial_ptr, result);
    EXPECT_EQ(replace, _test_value.load_relaxed());
  }

#define TEST_OP(name) { SCOPED_TRACE(XSTR(name)); name(); }

  void operator()() {
    TEST_OP(fetch_then_add)
    TEST_OP(fetch_then_sub)
    TEST_OP(add_then_fetch)
    TEST_OP(sub_then_fetch)
    TEST_OP(exchange)
    TEST_OP(compare_exchange)
  }

#undef TEST_OP
};

template<typename T>
T AtomicPointerTestSupport<T>::_test_values[10] = {};

template<typename T>
T* AtomicPointerTestSupport<T>::_initial_ptr = &_test_values[5];

TEST_VM(AtomicPointerTest, ptr_to_char) {
  AtomicPointerTestSupport<char>()();
}

TEST_VM(AtomicPointerTest, ptr_to_int32) {
  AtomicPointerTestSupport<int32_t>()();
}

TEST_VM(AtomicPointerTest, ptr_to_int64) {
  AtomicPointerTestSupport<int64_t>()();
}

// Test translation, including chaining.

struct TranslatedAtomicTestObject1 {
  int _value;

  // NOT default constructible.

  explicit TranslatedAtomicTestObject1(int value)
    : _value(value) {}
};

template<>
struct PrimitiveConversions::Translate<TranslatedAtomicTestObject1>
  : public std::true_type
{
  using Value = TranslatedAtomicTestObject1;
  using Decayed = int;

  static Decayed decay(Value x) { return x._value; }
  static Value recover(Decayed x) { return Value(x); }
};

struct TranslatedAtomicTestObject2 {
  TranslatedAtomicTestObject1 _value;

  static constexpr int DefaultObject1Value = 3;

  TranslatedAtomicTestObject2()
    : TranslatedAtomicTestObject2(TranslatedAtomicTestObject1(DefaultObject1Value))
  {}

  explicit TranslatedAtomicTestObject2(TranslatedAtomicTestObject1 value)
    : _value(value) {}
};

template<>
struct PrimitiveConversions::Translate<TranslatedAtomicTestObject2>
  : public std::true_type
{
  using Value = TranslatedAtomicTestObject2;
  using Decayed = TranslatedAtomicTestObject1;

  static Decayed decay(Value x) { return x._value; }
  static Value recover(Decayed x) { return Value(x); }
};

struct TranslatedAtomicByteObject {
  uint8_t _value;

  // NOT default constructible.

  explicit TranslatedAtomicByteObject(uint8_t value = 0) : _value(value) {}
};

template<>
struct PrimitiveConversions::Translate<TranslatedAtomicByteObject>
  : public std::true_type
{
  using Value = TranslatedAtomicByteObject;
  using Decayed = uint8_t;

  static Decayed decay(Value x) { return x._value; }
  static Value recover(Decayed x) { return Value(x); }
};

template<typename T>
static void test_atomic_translated_type() {
  // This works even if T is not default constructible.
  Atomic<T> _test_value{};

  using Translated = PrimitiveConversions::Translate<T>;

  EXPECT_EQ(0, Translated::decay(_test_value.load_relaxed()));
  _test_value.store_relaxed(Translated::recover(5));
  EXPECT_EQ(5, Translated::decay(_test_value.load_relaxed()));
  EXPECT_EQ(5, Translated::decay(_test_value.compare_exchange(Translated::recover(5),
                                                              Translated::recover(10))));
  EXPECT_EQ(10, Translated::decay(_test_value.load_relaxed()));

  EXPECT_EQ(10, Translated::decay(_test_value.exchange(Translated::recover(20))));
  EXPECT_EQ(20, Translated::decay(_test_value.load_relaxed()));
}

TEST_VM(AtomicTranslatedTypeTest, int_test) {
  test_atomic_translated_type<TranslatedAtomicTestObject1>();
}

TEST_VM(AtomicTranslatedTypeTest, byte_test) {
  test_atomic_translated_type<TranslatedAtomicByteObject>();
}

TEST_VM(AtomicTranslatedTypeTest, chain) {
  Atomic<TranslatedAtomicTestObject2> _test_value{};

  using Translated1 = PrimitiveConversions::Translate<TranslatedAtomicTestObject1>;
  using Translated2 = PrimitiveConversions::Translate<TranslatedAtomicTestObject2>;

  auto resolve = [&](TranslatedAtomicTestObject2 x) {
    return Translated1::decay(Translated2::decay(x));
  };

  auto construct = [&](int x) {
    return Translated2::recover(Translated1::recover(x));
  };

  EXPECT_EQ(TranslatedAtomicTestObject2::DefaultObject1Value,
            resolve(_test_value.load_relaxed()));
  _test_value.store_relaxed(construct(5));
  EXPECT_EQ(5, resolve(_test_value.load_relaxed()));
  EXPECT_EQ(5, resolve(_test_value.compare_exchange(construct(5), construct(10))));
  EXPECT_EQ(10, resolve(_test_value.load_relaxed()));
  EXPECT_EQ(10, resolve(_test_value.exchange(construct(20))));
  EXPECT_EQ(20, resolve(_test_value.load_relaxed()));
};

template<typename T>
static void test_value_access() {
  using AT = Atomic<T>;
  // In addition to verifying values are as expected, also verify the
  // operations are constexpr.
  static_assert(sizeof(T) == AT::value_size_in_bytes(), "value size differs");
  static_assert(0 == AT::value_offset_in_bytes(), "unexpected offset");
  // Also verify no unexpected increase in size for Atomic wrapper.
  static_assert(sizeof(T) == sizeof(AT), "unexpected size difference");
};

TEST_VM(AtomicValueAccessTest, access_char) {
  test_value_access<char>();
}

TEST_VM(AtomicValueAccessTest, access_bool) {
  test_value_access<bool>();
}

TEST_VM(AtomicValueAccessTest, access_int32) {
  test_value_access<int32_t>();
}

TEST_VM(AtomicValueAccessTest, access_int64) {
  test_value_access<int64_t>();
}

TEST_VM(AtomicValueAccessTest, access_ptr) {
  test_value_access<char*>();
}

TEST_VM(AtomicValueAccessTest, access_trans1) {
  test_value_access<TranslatedAtomicTestObject1>();
}

TEST_VM(AtomicValueAccessTest, access_trans2) {
  test_value_access<TranslatedAtomicTestObject2>();
}
