// Copyright 2017 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include namespace { // A struct with an interesting size for pointer arithmetic tests. struct S { char bytes[48]; }; using function_pointer = void(*)(); bool atomic_explicit_declarations_test() { BEGIN_TEST; [[gnu::unused]] fbl::atomic zero_char(0); [[gnu::unused]] fbl::atomic zero_schar(0); [[gnu::unused]] fbl::atomic zero_uchar(0); [[gnu::unused]] fbl::atomic zero_short(0); [[gnu::unused]] fbl::atomic zero_ushort(0); [[gnu::unused]] fbl::atomic zero_int(0); [[gnu::unused]] fbl::atomic zero_uint(0); [[gnu::unused]] fbl::atomic zero_long(0); [[gnu::unused]] fbl::atomic zero_ulong(0); [[gnu::unused]] fbl::atomic zero_llong(0); [[gnu::unused]] fbl::atomic zero_ullong(0); [[gnu::unused]] fbl::atomic zero_intptr_t(0); [[gnu::unused]] fbl::atomic zero_uintptr_t(0); [[gnu::unused]] fbl::atomic zero_size_t(0); [[gnu::unused]] fbl::atomic zero_ptrdiff_t(0); [[gnu::unused]] fbl::atomic zero_intmax_t(0); [[gnu::unused]] fbl::atomic zero_uintmax_t(0); [[gnu::unused]] fbl::atomic zero_int8_t(0); [[gnu::unused]] fbl::atomic zero_uint8_t(0); [[gnu::unused]] fbl::atomic zero_int16_t(0); [[gnu::unused]] fbl::atomic zero_uint16_t(0); [[gnu::unused]] fbl::atomic zero_int32_t(0); [[gnu::unused]] fbl::atomic zero_uint32_t(0); [[gnu::unused]] fbl::atomic zero_int64_t(0); [[gnu::unused]] fbl::atomic zero_uint64_t(0); [[gnu::unused]] fbl::atomic zero_int_least8_t(0); [[gnu::unused]] fbl::atomic zero_uint_least8_t(0); [[gnu::unused]] fbl::atomic zero_int_least16_t(0); [[gnu::unused]] fbl::atomic zero_uint_least16_t(0); [[gnu::unused]] fbl::atomic zero_int_least32_t(0); [[gnu::unused]] fbl::atomic zero_uint_least32_t(0); [[gnu::unused]] fbl::atomic zero_int_least64_t(0); [[gnu::unused]] fbl::atomic zero_uint_least64_t(0); [[gnu::unused]] fbl::atomic zero_int_fast8_t(0); [[gnu::unused]] fbl::atomic zero_uint_fast8_t(0); [[gnu::unused]] fbl::atomic zero_int_fast16_t(0); [[gnu::unused]] fbl::atomic zero_uint_fast16_t(0); [[gnu::unused]] fbl::atomic zero_int_fast32_t(0); [[gnu::unused]] fbl::atomic zero_uint_fast32_t(0); [[gnu::unused]] fbl::atomic zero_int_fast64_t(0); [[gnu::unused]] fbl::atomic zero_uint_fast64_t(0); [[gnu::unused]] fbl::atomic zero_bool(false); [[gnu::unused]] fbl::atomic zero_void_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_void_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_volatile_void_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_volatile_void_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_int_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_int_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_volatile_int_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_volatile_int_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_S_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_S_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_volatile_S_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_const_volatile_S_pointer(nullptr); [[gnu::unused]] fbl::atomic zero_function_pointer(nullptr); END_TEST; } bool atomic_using_declarations_test() { BEGIN_TEST; [[gnu::unused]] fbl::atomic_char zero_char(0); [[gnu::unused]] fbl::atomic_schar zero_schar(0); [[gnu::unused]] fbl::atomic_uchar zero_uchar(0); [[gnu::unused]] fbl::atomic_short zero_short(0); [[gnu::unused]] fbl::atomic_ushort zero_ushort(0); [[gnu::unused]] fbl::atomic_int zero_int(0); [[gnu::unused]] fbl::atomic_uint zero_uint(0); [[gnu::unused]] fbl::atomic_long zero_long(0); [[gnu::unused]] fbl::atomic_ulong zero_ulong(0); [[gnu::unused]] fbl::atomic_llong zero_llong(0); [[gnu::unused]] fbl::atomic_ullong zero_ullong(0); [[gnu::unused]] fbl::atomic_intptr_t zero_intptr_t(0); [[gnu::unused]] fbl::atomic_uintptr_t zero_uintptr_t(0); [[gnu::unused]] fbl::atomic_size_t zero_size_t(0); [[gnu::unused]] fbl::atomic_ptrdiff_t zero_ptrdiff_t(0); [[gnu::unused]] fbl::atomic_intmax_t zero_intmax_t(0); [[gnu::unused]] fbl::atomic_uintmax_t zero_uintmax_t(0); [[gnu::unused]] fbl::atomic_int8_t zero_int8_t(0); [[gnu::unused]] fbl::atomic_uint8_t zero_uint8_t(0); [[gnu::unused]] fbl::atomic_int16_t zero_int16_t(0); [[gnu::unused]] fbl::atomic_uint16_t zero_uint16_t(0); [[gnu::unused]] fbl::atomic_int32_t zero_int32_t(0); [[gnu::unused]] fbl::atomic_uint32_t zero_uint32_t(0); [[gnu::unused]] fbl::atomic_int64_t zero_int64_t(0); [[gnu::unused]] fbl::atomic_uint64_t zero_uint64_t(0); [[gnu::unused]] fbl::atomic_int_least8_t zero_int_least8_t(0); [[gnu::unused]] fbl::atomic_uint_least8_t zero_uint_least8_t(0); [[gnu::unused]] fbl::atomic_int_least16_t zero_int_least16_t(0); [[gnu::unused]] fbl::atomic_uint_least16_t zero_uint_least16_t(0); [[gnu::unused]] fbl::atomic_int_least32_t zero_int_least32_t(0); [[gnu::unused]] fbl::atomic_uint_least32_t zero_uint_least32_t(0); [[gnu::unused]] fbl::atomic_int_least64_t zero_int_least64_t(0); [[gnu::unused]] fbl::atomic_uint_least64_t zero_uint_least64_t(0); [[gnu::unused]] fbl::atomic_int_fast8_t zero_int_fast8_t(0); [[gnu::unused]] fbl::atomic_uint_fast8_t zero_uint_fast8_t(0); [[gnu::unused]] fbl::atomic_int_fast16_t zero_int_fast16_t(0); [[gnu::unused]] fbl::atomic_uint_fast16_t zero_uint_fast16_t(0); [[gnu::unused]] fbl::atomic_int_fast32_t zero_int_fast32_t(0); [[gnu::unused]] fbl::atomic_uint_fast32_t zero_uint_fast32_t(0); [[gnu::unused]] fbl::atomic_int_fast64_t zero_int_fast64_t(0); [[gnu::unused]] fbl::atomic_uint_fast64_t zero_uint_fast64_t(0); [[gnu::unused]] fbl::atomic_bool zero_bool(false); END_TEST; } // To increase test readability after this point, static_assert that // most of these are the same as fbl::atomic. That // way no one has to read a million lines of test code about // fbl::atomic_uint_least32_t. template constexpr bool IsSameAsSomeBuiltin() { return fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value || fbl::is_same::value; } static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); static_assert(IsSameAsSomeBuiltin(), ""); // We should be able to instantiate fbl::atomics of enum type as well. enum unspecified_enum { kUnspecifiedValue = 23, }; __UNUSED fbl::atomic atomic_unspecified_enum; enum specified_enum_char : char { kSpecifiedValue_char = 23, }; __UNUSED fbl::atomic atomic_specified_enum_char; enum specified_enum_signed_char : signed char { kSpecifiedValue_signed_char = 23, }; __UNUSED fbl::atomic atomic_specified_enum_signed_char; enum specified_enum_unsigned_char : unsigned char { kSpecifiedValue_unsigned_char = 23, }; __UNUSED fbl::atomic atomic_specified_enum_unsigned_char; enum specified_enum_short : short { kSpecifiedValue_short = 23, }; __UNUSED fbl::atomic atomic_specified_enum_short; enum specified_enum_unsigned_short : unsigned short { kSpecifiedValue_unsigned_short = 23, }; __UNUSED fbl::atomic atomic_specified_enum_unsigned_short; enum specified_enum_int : int { kSpecifiedValue_int = 23, }; __UNUSED fbl::atomic atomic_specified_enum_int; enum specified_enum_unsigned_int : unsigned int { kSpecifiedValue_unsigned_int = 23, }; __UNUSED fbl::atomic atomic_specified_enum_unsigned_int; enum specified_enum_long : long { kSpecifiedValue_long = 23, }; __UNUSED fbl::atomic atomic_specified_enum_long; enum specified_enum_unsigned_long : unsigned long{ kSpecifiedValue_unsigned_long = 23, }; __UNUSED fbl::atomic atomic_specified_enum_unsigned_long; enum specified_enum_long_long : long long { kSpecifiedValue_long_long = 23, }; __UNUSED fbl::atomic atomic_specified_enum_long_long; enum specified_enum_unsigned_long_long : unsigned long long { kSpecifiedValue_unsigned_long_long = 23, }; __UNUSED fbl::atomic atomic_specified_enum_unsigned_long_long; enum specified_enum_bool : bool { kSpecifiedValue_bool = true, }; __UNUSED fbl::atomic atomic_specified_enum_bool; enum struct unspecified_struct_enum { kUnspecifiedValueStruct = 23, }; __UNUSED fbl::atomic atomic_unspecified_struct_enum; enum struct specified_struct_enum_char : char { kSpecifiedStructValue_char = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_char; enum struct specified_struct_enum_signed_char : signed char { kSpecifiedStructValue_signed_char = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_signed_char; enum struct specified_struct_enum_unsigned_char : unsigned char { kSpecifiedStructValue_unsigned_char = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_unsigned_char; enum struct specified_struct_enum_short : short { kSpecifiedStructValue_short = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_short; enum struct specified_struct_enum_unsigned_short : unsigned short { kSpecifiedStructValue_unsigned_short = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_unsigned_short; enum struct specified_struct_enum_int : int { kSpecifiedStructValue_int = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_int; enum struct specified_struct_enum_unsigned_int : unsigned int { kSpecifiedStructValue_unsigned_int = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_unsigned_int; enum struct specified_struct_enum_long : long { kSpecifiedStructValue_long = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_long; enum struct specified_struct_enum_unsigned_long : unsigned long{ kSpecifiedStructValue_unsigned_long = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_unsigned_long; enum struct specified_struct_enum_long_long : long long { kSpecifiedStructValue_long_long = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_long_long; enum struct specified_struct_enum_unsigned_long_long : unsigned long long { kSpecifiedStructValue_unsigned_long_long = 23, }; __UNUSED fbl::atomic atomic_specified_struct_enum_unsigned_long_long; enum struct specified_struct_enum_bool : bool { kSpecifiedStructValue_bool = true, }; __UNUSED fbl::atomic atomic_specified_struct_enum_bool; bool atomic_wont_compile_test() { BEGIN_TEST; // fbl::atomic only supports integer, enum, and pointer types. #if TEST_WILL_NOT_COMPILE || 0 struct not_integral {}; fbl::atomic not_integral; #endif #if TEST_WILL_NOT_COMPILE || 0 fbl::atomic not_integral; #endif #if TEST_WILL_NOT_COMPILE || 0 fbl::atomic not_integral; #endif END_TEST; } fbl::memory_order orders[] = { fbl::memory_order_relaxed, fbl::memory_order_acquire, fbl::memory_order_release, fbl::memory_order_acq_rel, fbl::memory_order_seq_cst, }; // Bunch of machinery for arithmetic tests. template using ordinary_op = T (*)(T*, T); template using atomic_op = T (*)(fbl::atomic*, T, fbl::memory_order); template using volatile_op = T (*)(volatile fbl::atomic*, T, fbl::memory_order); template struct TestCase { ordinary_op ordinary; atomic_op nonmember_atomic; atomic_op member_atomic; volatile_op nonmember_volatile; volatile_op member_volatile; }; template T test_values[] = { 0, 1, 23, std::numeric_limits::min() / 4, std::numeric_limits::max() / 4, }; template <> bool test_values[] = { false, true, true, false, true, }; template <> void* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> const void* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> volatile void* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> const volatile void* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> int* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> const int* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> volatile int* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; template <> const volatile int* test_values[] = { &test_values[0], &test_values[1], &test_values[2], &test_values[3], &test_values[4], }; S test_values_of_S[] = { {}, {}, {}, {} }; template <> S* test_values[] = { &test_values_of_S[0], &test_values_of_S[1], &test_values_of_S[2], &test_values_of_S[3], nullptr, }; template <> const S* test_values[] = { &test_values_of_S[0], &test_values_of_S[1], &test_values_of_S[2], &test_values_of_S[3], nullptr, }; template <> volatile S* test_values[] = { &test_values_of_S[0], &test_values_of_S[1], &test_values_of_S[2], &test_values_of_S[3], nullptr, }; template <> const volatile S* test_values[] = { &test_values_of_S[0], &test_values_of_S[1], &test_values_of_S[2], &test_values_of_S[3], nullptr, }; // Try to force each of these to be different so that the test // continues working under ICF. The CAS tests compare function pointer // values, so it's important that these have different addresses. static volatile int volatile_0; void nothing_0() { volatile_0 = 0; } static volatile int volatile_1; void nothing_1() { volatile_1 = 1; } static volatile int volatile_2; void nothing_2() { volatile_2 = 2; } static volatile int volatile_3; void nothing_3() { volatile_3 = 3; } template <> function_pointer test_values[] = { ¬hing_0, ¬hing_1, ¬hing_2, ¬hing_3, nullptr, }; template TestCase test_cases[] = { { [](T* ptr_to_a, T b) -> T { T a = *ptr_to_a; *ptr_to_a = static_cast(a + b); return a; }, fbl::atomic_fetch_add, [](fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_add(b, order); }, fbl::atomic_fetch_add, [](volatile fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_add(b, order); }, }, { [](T* ptr_to_a, T b) -> T { T a = *ptr_to_a; *ptr_to_a = static_cast(a & b); return a; }, fbl::atomic_fetch_and, [](fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_and(b, order); }, fbl::atomic_fetch_and, [](volatile fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_and(b, order); }, }, { [](T* ptr_to_a, T b) -> T { T a = *ptr_to_a; *ptr_to_a = static_cast(a | b); return a; }, fbl::atomic_fetch_or, [](fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_or(b, order); }, fbl::atomic_fetch_or, [](volatile fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_or(b, order); }, }, { [](T* ptr_to_a, T b) -> T { T a = *ptr_to_a; *ptr_to_a = static_cast(a ^ b); return a; }, fbl::atomic_fetch_xor, [](fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_xor(b, order); }, fbl::atomic_fetch_xor, [](volatile fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_xor(b, order); }, }, }; template TestCase subtraction_test_case = { [](T* ptr_to_a, T b) -> T { T a = *ptr_to_a; *ptr_to_a = static_cast(a - b); return a; }, fbl::atomic_fetch_sub, [](fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_sub(b, order); }, fbl::atomic_fetch_sub, [](volatile fbl::atomic* ptr_to_atomic_a, T b, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_sub(b, order); }, }; template bool math_test() { for (const T original_left : test_values) { for (T right : test_values) { for (const auto& order : orders) { for (auto test_case : test_cases) { { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } } // Let's not worry about signed subtraction UB. if (fbl::is_unsigned::value) { { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(subtraction_test_case.ordinary(&left, right), subtraction_test_case.member_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(subtraction_test_case.ordinary(&left, right), subtraction_test_case.nonmember_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(subtraction_test_case.ordinary(&left, right), subtraction_test_case.member_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(subtraction_test_case.ordinary(&left, right), subtraction_test_case.nonmember_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } } } } } return true; } template using ordinary_pointer_op = T (*)(T*, ptrdiff_t); template using atomic_pointer_op = T (*)(fbl::atomic*, ptrdiff_t, fbl::memory_order); template using volatile_pointer_op = T (*)(volatile fbl::atomic*, ptrdiff_t, fbl::memory_order); template struct PointerTestCase { ordinary_pointer_op ordinary; atomic_pointer_op nonmember_atomic; atomic_pointer_op member_atomic; volatile_pointer_op nonmember_volatile; volatile_pointer_op member_volatile; }; template PointerTestCase pointer_add_test_case = { [](T* ptr_to_a, ptrdiff_t d) -> T { T a = *ptr_to_a; *ptr_to_a = a + d; return a; }, [](fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return fbl::atomic_fetch_add(ptr_to_atomic_a, d, order); }, [](fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_add(d, order); }, [](volatile fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return fbl::atomic_fetch_add(ptr_to_atomic_a, d, order); }, [](volatile fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_add(d, order); }, }; template PointerTestCase pointer_sub_test_case = { [](T* ptr_to_a, ptrdiff_t d) -> T { T a = *ptr_to_a; *ptr_to_a = a - d; return a; }, [](fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return fbl::atomic_fetch_sub(ptr_to_atomic_a, d, order); }, [](fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_sub(d, order); }, [](volatile fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return fbl::atomic_fetch_sub(ptr_to_atomic_a, d, order); }, [](volatile fbl::atomic* ptr_to_atomic_a, ptrdiff_t d, fbl::memory_order order) -> T { return ptr_to_atomic_a->fetch_sub(d, order); }, }; template bool pointer_add_test() { static_assert(fbl::is_pointer::value, ""); ptrdiff_t right = 2; const auto& test_case = pointer_add_test_case; for (const T original_left : test_values) { for (const auto& order : orders) { { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } } right -= 1; } return true; } template bool pointer_sub_test() { static_assert(fbl::is_pointer::value, ""); ptrdiff_t right = -2; const auto& test_case = pointer_sub_test_case; for (const T original_left : test_values) { for (const auto& order : orders) { { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_atomic(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.member_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } { volatile fbl::atomic atomic_left(original_left); T left = original_left; ASSERT_EQ(test_case.ordinary(&left, right), test_case.nonmember_volatile(&atomic_left, right, order), "Atomic and ordinary math differ"); ASSERT_EQ(left, atomic_load(&atomic_left), "Atomic and ordinary math differ"); } } right += 1; } return true; } template bool load_store_test() { fbl::atomic atomic_value; for (T value : test_values) { atomic_value.store(value); ASSERT_EQ(atomic_value.load(), value, "Member load/store busted"); } for (T value : test_values) { fbl::atomic_store(&atomic_value, value); ASSERT_EQ(atomic_load(&atomic_value), value, "Nonmember load/store busted"); } volatile fbl::atomic volatile_value; for (T value : test_values) { volatile_value.store(value); ASSERT_EQ(volatile_value.load(), value, "Member load/store busted"); } for (T value : test_values) { fbl::atomic_store(&volatile_value, value); ASSERT_EQ(atomic_load(&volatile_value), value, "Nonmember load/store busted"); } return true; } template bool exchange_test() { T last_value = test_values[0]; fbl::atomic atomic_value(last_value); for (T value : test_values) { ASSERT_EQ(atomic_value.load(), last_value, "Member exchange busted"); ASSERT_EQ(atomic_value.exchange(value), last_value, "Member exchange busted"); last_value = value; } last_value = test_values[0]; atomic_value.store(last_value); for (T value : test_values) { ASSERT_EQ(fbl::atomic_load(&atomic_value), last_value, "Nonmember exchange busted"); ASSERT_EQ(fbl::atomic_exchange(&atomic_value, value), last_value, "Nonmember exchange busted"); last_value = value; } last_value = test_values[0]; volatile fbl::atomic volatile_value(last_value); for (T value : test_values) { ASSERT_EQ(volatile_value.load(), last_value, "Member exchange busted"); ASSERT_EQ(volatile_value.exchange(value), last_value, "Member exchange busted"); last_value = value; } last_value = test_values[0]; volatile_value.store(last_value); for (T value : test_values) { ASSERT_EQ(fbl::atomic_load(&volatile_value), last_value, "Nonmember exchange busted"); ASSERT_EQ(fbl::atomic_exchange(&volatile_value, value), last_value, "Nonmember exchange busted"); last_value = value; } return true; } template struct cas_function { bool (*function)(fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order); bool can_spuriously_fail; }; template cas_function cas_functions[] = { {fbl::atomic_compare_exchange_weak, true}, {fbl::atomic_compare_exchange_strong, false}, {[](fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order) { return atomic_ptr->compare_exchange_weak(expected, desired, success_order, failure_order); }, true}, {[](fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order) { return atomic_ptr->compare_exchange_strong(expected, desired, success_order, failure_order); }, false}, }; template struct volatile_cas_function { bool (*function)(volatile fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order); bool can_spuriously_fail; }; template volatile_cas_function volatile_cas_functions[] = { {fbl::atomic_compare_exchange_weak, true}, {fbl::atomic_compare_exchange_strong, false}, {[](volatile fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order) { return atomic_ptr->compare_exchange_weak(expected, desired, success_order, failure_order); }, true}, {[](volatile fbl::atomic* atomic_ptr, T* expected, T desired, fbl::memory_order success_order, fbl::memory_order failure_order) { return atomic_ptr->compare_exchange_strong(expected, desired, success_order, failure_order); }, false}}; enum cas_slots { kExpected = 0, kActual = 1, kDesired = 2, }; template T cas_test_values[] = { 22, 23, 24, }; template <> bool cas_test_values[] = { false, true, false, }; template <> void* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> const void* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> volatile void* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> const volatile void* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> int* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> const int* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> volatile int* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; template <> const volatile int* cas_test_values[] = { &cas_test_values[0], &cas_test_values[1], &cas_test_values[2], }; S cas_test_values_of_S[] = { {}, {} }; template <> S* cas_test_values[] = { &cas_test_values_of_S[0], &cas_test_values_of_S[1], nullptr, }; template <> const S* cas_test_values[] = { &cas_test_values_of_S[0], &cas_test_values_of_S[1], nullptr, }; template <> volatile S* cas_test_values[] = { &cas_test_values_of_S[0], &cas_test_values_of_S[1], nullptr, }; template <> const volatile S* cas_test_values[] = { &cas_test_values_of_S[0], &cas_test_values_of_S[1], nullptr, }; template <> function_pointer cas_test_values[] = { ¬hing_0, ¬hing_1, nullptr, }; template bool compare_exchange_test() { for (auto cas : cas_functions) { for (const auto& success_order : orders) { for (const auto& failure_order : orders) { { // Failure case T actual = cas_test_values[kActual]; fbl::atomic atomic_value(actual); T expected = cas_test_values[kExpected]; T desired = cas_test_values[kDesired]; EXPECT_FALSE(cas.function(&atomic_value, &expected, desired, success_order, failure_order), "compare-exchange shouldn't have succeeded!"); EXPECT_EQ(expected, actual, "compare-exchange didn't report actual value!"); } { // Success case T actual = cas_test_values[kActual]; fbl::atomic atomic_value(actual); T expected = actual; T desired = cas_test_values[kDesired]; // Some compare-and-swap functions can spuriously fail. bool succeeded = cas.function(&atomic_value, &expected, desired, success_order, failure_order); if (!cas.can_spuriously_fail) { EXPECT_TRUE(succeeded, "compare-exchange should've succeeded!"); } EXPECT_EQ(expected, actual, "compare-exchange didn't report actual value!"); } } } } for (auto cas : volatile_cas_functions) { for (const auto& success_order : orders) { for (const auto& failure_order : orders) { { // Failure case T actual = cas_test_values[kActual]; fbl::atomic atomic_value(actual); T expected = cas_test_values[kExpected]; T desired = cas_test_values[kDesired]; EXPECT_FALSE(cas.function(&atomic_value, &expected, desired, success_order, failure_order), "compare-exchange shouldn't have succeeded!"); EXPECT_EQ(expected, actual, "compare-exchange didn't report actual value!"); } { // Success case T actual = cas_test_values[kActual]; fbl::atomic atomic_value(actual); T expected = actual; T desired = cas_test_values[kDesired]; // Compare-and-swap can spuriously fail. // Some compare-and-swap functions can spuriously fail. bool succeeded = cas.function(&atomic_value, &expected, desired, success_order, failure_order); if (!cas.can_spuriously_fail) { EXPECT_TRUE(succeeded, "compare-exchange should've succeeded!"); } EXPECT_EQ(expected, actual, "compare-exchange didn't report actual value!"); } } } } return true; } // Actual test cases on operations for each builtin type. bool atomic_math_test() { BEGIN_TEST; ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); ASSERT_TRUE(math_test()); END_TEST; } bool atomic_pointer_math_test() { BEGIN_TEST; ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_add_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); ASSERT_TRUE(pointer_sub_test()); // Note that there is no void pointer or function pointer math, // and so no tests of them. END_TEST; } bool atomic_load_store_test() { BEGIN_TEST; ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); ASSERT_TRUE(load_store_test()); END_TEST; } bool atomic_exchange_test() { BEGIN_TEST; ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); ASSERT_TRUE(exchange_test()); END_TEST; } bool atomic_compare_exchange_test() { BEGIN_TEST; ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); ASSERT_TRUE(compare_exchange_test()); END_TEST; } // Code wants to rely on the ABI of fbl::atomic types. This means // matching the underlying types' size and alignment, and the class // being standard layout. static_assert(sizeof(fbl::atomic) == sizeof(char), ""); static_assert(sizeof(fbl::atomic) == sizeof(signed char), ""); static_assert(sizeof(fbl::atomic) == sizeof(unsigned char), ""); static_assert(sizeof(fbl::atomic) == sizeof(short), ""); static_assert(sizeof(fbl::atomic) == sizeof(unsigned short), ""); static_assert(sizeof(fbl::atomic) == sizeof(int), ""); static_assert(sizeof(fbl::atomic) == sizeof(unsigned int), ""); static_assert(sizeof(fbl::atomic) == sizeof(long), ""); static_assert(sizeof(fbl::atomic) == sizeof(unsigned long), ""); static_assert(sizeof(fbl::atomic) == sizeof(long long), ""); static_assert(sizeof(fbl::atomic) == sizeof(unsigned long long), ""); static_assert(sizeof(fbl::atomic) == sizeof(bool), ""); static_assert(sizeof(fbl::atomic) == sizeof(void*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const void*), ""); static_assert(sizeof(fbl::atomic) == sizeof(volatile void*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const volatile void*), ""); static_assert(sizeof(fbl::atomic) == sizeof(int*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const int*), ""); static_assert(sizeof(fbl::atomic) == sizeof(volatile int*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const volatile int*), ""); static_assert(sizeof(fbl::atomic) == sizeof(S*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const S*), ""); static_assert(sizeof(fbl::atomic) == sizeof(volatile S*), ""); static_assert(sizeof(fbl::atomic) == sizeof(const volatile S*), ""); static_assert(sizeof(fbl::atomic) == sizeof(function_pointer), ""); static_assert(alignof(fbl::atomic) == alignof(char), ""); static_assert(alignof(fbl::atomic) == alignof(signed char), ""); static_assert(alignof(fbl::atomic) == alignof(unsigned char), ""); static_assert(alignof(fbl::atomic) == alignof(short), ""); static_assert(alignof(fbl::atomic) == alignof(unsigned short), ""); static_assert(alignof(fbl::atomic) == alignof(int), ""); static_assert(alignof(fbl::atomic) == alignof(unsigned int), ""); static_assert(alignof(fbl::atomic) == alignof(long), ""); static_assert(alignof(fbl::atomic) == alignof(unsigned long), ""); static_assert(alignof(fbl::atomic) == alignof(long long), ""); static_assert(alignof(fbl::atomic) == alignof(unsigned long long), ""); static_assert(alignof(fbl::atomic) == alignof(bool), ""); static_assert(alignof(fbl::atomic) == alignof(void*), ""); static_assert(alignof(fbl::atomic) == alignof(const void*), ""); static_assert(alignof(fbl::atomic) == alignof(volatile void*), ""); static_assert(alignof(fbl::atomic) == alignof(const volatile void*), ""); static_assert(alignof(fbl::atomic) == alignof(int*), ""); static_assert(alignof(fbl::atomic) == alignof(const int*), ""); static_assert(alignof(fbl::atomic) == alignof(volatile int*), ""); static_assert(alignof(fbl::atomic) == alignof(const volatile int*), ""); static_assert(alignof(fbl::atomic) == alignof(S*), ""); static_assert(alignof(fbl::atomic) == alignof(const S*), ""); static_assert(alignof(fbl::atomic) == alignof(volatile S*), ""); static_assert(alignof(fbl::atomic) == alignof(const volatile S*), ""); static_assert(alignof(fbl::atomic) == alignof(function_pointer), ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); static_assert(fbl::is_standard_layout>::value, ""); bool atomic_fence_test() { BEGIN_TEST; for (const auto& order : orders) { atomic_thread_fence(order); atomic_signal_fence(order); } END_TEST; } bool atomic_init_test() { BEGIN_TEST; fbl::atomic_uint32_t atomic1; fbl::atomic_init(&atomic1, 1u); EXPECT_EQ(1u, atomic1.load()); fbl::atomic_uint32_t atomic2; volatile fbl::atomic_uint32_t* vatomic2 = &atomic2; fbl::atomic_init(vatomic2, 2u); EXPECT_EQ(2u, atomic2.load()); END_TEST; } } // namespace BEGIN_TEST_CASE(atomic_tests) RUN_NAMED_TEST("Atomic explicit declarations test", atomic_explicit_declarations_test) RUN_NAMED_TEST("Atomic using declarations test", atomic_using_declarations_test) RUN_NAMED_TEST("Atomic won't compile test", atomic_wont_compile_test) RUN_NAMED_TEST("Atomic math test", atomic_math_test) RUN_NAMED_TEST("Atomic pointer math test", atomic_pointer_math_test) RUN_NAMED_TEST("Atomic load/store test", atomic_load_store_test) RUN_NAMED_TEST("Atomic exchange test", atomic_exchange_test) RUN_NAMED_TEST("Atomic compare-exchange test", atomic_compare_exchange_test) RUN_NAMED_TEST("Atomic fence test", atomic_fence_test) RUN_NAMED_TEST("Atomic init test", atomic_init_test) END_TEST_CASE(atomic_tests);