/* * 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 only verify functionality. They don't verify // atomicity. template struct AtomicIntegerArithmeticTestSupport { Atomic _test_value; static constexpr T _old_value = static_cast(UCONST64(0x2000000020000)); static constexpr T _change_value = static_cast(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()(); } TEST_VM(AtomicIntegerTest, arith_uint32) { AtomicIntegerArithmeticTestSupport()(); } TEST_VM(AtomicIntegerTest, arith_int64) { AtomicIntegerArithmeticTestSupport()(); } TEST_VM(AtomicIntegerTest, arith_uint64) { AtomicIntegerArithmeticTestSupport()(); } template struct AtomicByteAndIntegerXchgTestSupport { Atomic _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; Support().test(); } TEST_VM(AtomicIntegerTest, xchg_int32) { using Support = AtomicByteAndIntegerXchgTestSupport; Support().test(); } TEST_VM(AtomicIntegerTest, xchg_int64) { using Support = AtomicByteAndIntegerXchgTestSupport; Support().test(); } template struct AtomicIntegerCmpxchgTestSupport { Atomic _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; Support().test(); } TEST_VM(AtomicIntegerTest, cmpxchg_int64) { // Check if 64-bit atomics are available on the machine. using Support = AtomicIntegerCmpxchgTestSupport; Support().test(); } template struct AtomicIntegerCmpsetTestSupport { Atomic _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; Support().test(); } TEST_VM(AtomicIntegerTest, cmpset_int64) { // Check if 64-bit atomics are available on the machine. using Support = AtomicIntegerCmpsetTestSupport; Support().test(); } struct AtomicXchgAndCmpxchg1ByteStressSupport { char _default_val; int _base; Atomic _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 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 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(i); } } void test_exchange() { struct StressWithExchange { void operator()(Atomic& atomic, char compare_value, char new_value) { EXPECT_EQ(compare_value, atomic.exchange(new_value)); } }; test(); } void test_compare_exchange() { struct StressWithCompareExchange { void operator()(Atomic& atomic, char compare_value, char new_value) { EXPECT_EQ(compare_value, atomic.compare_exchange(compare_value, new_value)); } }; test(); } }; TEST_VM(AtomicByteTest, stress_xchg) { AtomicXchgAndCmpxchg1ByteStressSupport support; support.test_exchange(); } TEST_VM(AtomicByteTest, stress_cmpxchg) { AtomicXchgAndCmpxchg1ByteStressSupport support; support.test_compare_exchange(); } template struct AtomicTestSupport { Atomic _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 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::test(); } enum class AtomicEnumTestScoped { A, B, C }; TEST_VM(AtomicEnumTest, scoped_enum) { const AtomicEnumTestScoped B = AtomicEnumTestScoped::B; const AtomicEnumTestScoped C = AtomicEnumTestScoped::C; AtomicTestSupport::test(); } 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::test(); } 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::test(); } TEST_VM(AtomicByteTest, char_test) { const char B = 0xB; const char C = 0xC; AtomicTestSupport::test(); } TEST_VM(AtomicByteTest, bool_test) { const bool B = true; const bool C = false; AtomicTestSupport::test(); } template struct AtomicBitopsTestSupport { Atomic _test_value; // At least one byte differs between _old_value and _old_value op _change_value. static constexpr T _old_value = static_cast(UCONST64(0x7f5300007f530044)); static constexpr T _change_value = static_cast(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()(); } TEST_VM(AtomicBitopsTest, uint32) { AtomicBitopsTestSupport()(); } TEST_VM(AtomicBitopsTest, int64) { AtomicBitopsTestSupport()(); } TEST_VM(AtomicBitopsTest, uint64) { AtomicBitopsTestSupport()(); } template struct AtomicPointerTestSupport { static T _test_values[10]; static T* _initial_ptr; Atomic _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 T AtomicPointerTestSupport::_test_values[10] = {}; template T* AtomicPointerTestSupport::_initial_ptr = &_test_values[5]; TEST_VM(AtomicPointerTest, ptr_to_char) { AtomicPointerTestSupport()(); } TEST_VM(AtomicPointerTest, ptr_to_int32) { AtomicPointerTestSupport()(); } TEST_VM(AtomicPointerTest, ptr_to_int64) { AtomicPointerTestSupport()(); } // Test translation, including chaining. struct TranslatedAtomicTestObject1 { int _value; // NOT default constructible. explicit TranslatedAtomicTestObject1(int value) : _value(value) {} }; template<> struct PrimitiveConversions::Translate : 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 : 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 : 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 static void test_atomic_translated_type() { // This works even if T is not default constructible. Atomic _test_value{}; using Translated = PrimitiveConversions::Translate; 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(); } TEST_VM(AtomicTranslatedTypeTest, byte_test) { test_atomic_translated_type(); } TEST_VM(AtomicTranslatedTypeTest, chain) { Atomic _test_value{}; using Translated1 = PrimitiveConversions::Translate; using Translated2 = PrimitiveConversions::Translate; 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 static void test_value_access() { using AT = Atomic; // 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(); } TEST_VM(AtomicValueAccessTest, access_bool) { test_value_access(); } TEST_VM(AtomicValueAccessTest, access_int32) { test_value_access(); } TEST_VM(AtomicValueAccessTest, access_int64) { test_value_access(); } TEST_VM(AtomicValueAccessTest, access_ptr) { test_value_access(); } TEST_VM(AtomicValueAccessTest, access_trans1) { test_value_access(); } TEST_VM(AtomicValueAccessTest, access_trans2) { test_value_access(); }