#include "test/jemalloc_test.h" /* * We *almost* have consistent short names (e.g. "u32" for uint32_t, "b" for * bool, etc. The one exception is that the short name for void * is "p" in * some places and "ptr" in others. In the long run it would be nice to unify * these, but in the short run we'll use this shim. */ #define assert_p_eq assert_ptr_eq /* * t: the non-atomic type, like "uint32_t". * ta: the short name for the type, like "u32". * val[1,2,3]: Values of the given type. The CAS tests use val2 for expected, * and val3 for desired. */ #define DO_TESTS(t, ta, val1, val2, val3) do { \ t val; \ t raw_atomic; \ t expected; \ bool success; \ /* This (along with the load below) also tests ATOMIC_LOAD. */ \ atomic_##ta##_t atom = ATOMIC_INIT(val1); \ \ /* ATOMIC_INIT and load. */ \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, "Load or init failed"); \ \ /* Store. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ atomic_store_##ta(&atom, val2, ATOMIC_RELAXED); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val2, val, "Store failed"); \ \ /* Exchange. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_exchange_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, "Exchange returned invalid value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val2, val, "Exchange store invalid value"); \ \ /* \ * Weak CAS. Spurious failures are allowed, so we loop a few \ * times. \ */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ success = false; \ for (int i = 0; i < 10 && !success; i++) { \ expected = val2; \ success = atomic_compare_exchange_weak_##ta(&atom, \ &expected, val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, expected, \ "CAS should update expected"); \ } \ assert_b_eq(val1 == val2, success, \ "Weak CAS did the wrong state update"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ if (success) { \ assert_##ta##_eq(val3, val, \ "Successful CAS should update atomic"); \ } else { \ assert_##ta##_eq(val1, val, \ "Unsuccessful CAS should not update atomic"); \ } \ \ /* Strong CAS. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ expected = val2; \ success = atomic_compare_exchange_strong_##ta(&atom, &expected, \ val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \ assert_b_eq(val1 == val2, success, \ "Strong CAS did the wrong state update"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ if (success) { \ assert_##ta##_eq(val3, val, \ "Successful CAS should update atomic"); \ } else { \ assert_##ta##_eq(val1, val, \ "Unsuccessful CAS should not update atomic"); \ } \ \ \ /* Previous atomics API. */ \ \ /* Read. */ \ raw_atomic = val1; \ val = atomic_read_##ta(&raw_atomic); \ assert_##ta##_eq(val1, val, "Read failed"); \ \ /* Write. */ \ raw_atomic = val1; \ atomic_write_##ta(&raw_atomic, val2); \ assert_##ta##_eq(val2, raw_atomic, "Write failed"); \ \ /* CAS. */ \ raw_atomic = val1; \ success = !atomic_cas_##ta(&raw_atomic, val2, val3); \ assert_b_eq(val1 == val2, success, \ "CAS did the wrong state update"); \ val = raw_atomic; \ if (success) { \ assert_##ta##_eq(val3, val, \ "Successful CAS should update atomic"); \ } else { \ assert_##ta##_eq(val1, val, \ "Unsuccessful CAS should not update atomic"); \ } \ } while (0) #define DO_INTEGER_TESTS(t, ta, val1, val2) do { \ atomic_##ta##_t atom; \ t val; \ t raw_atomic; \ \ /* Fetch-add. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_add_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, \ "Fetch-add should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1 + val2, val, \ "Fetch-add should update atomic"); \ \ /* Fetch-sub. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_sub_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, \ "Fetch-sub should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1 - val2, val, \ "Fetch-sub should update atomic"); \ \ /* Fetch-and. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_and_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, \ "Fetch-and should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1 & val2, val, \ "Fetch-and should update atomic"); \ \ /* Fetch-or. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_or_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, \ "Fetch-or should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1 | val2, val, \ "Fetch-or should update atomic"); \ \ /* Fetch-xor. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_xor_##ta(&atom, val2, ATOMIC_RELAXED); \ assert_##ta##_eq(val1, val, \ "Fetch-xor should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ assert_##ta##_eq(val1 ^ val2, val, \ "Fetch-xor should update atomic"); \ \ /* Previous atomics API. */ \ \ /* Add. */ \ raw_atomic = val1; \ val = atomic_add_##ta(&raw_atomic, val2); \ assert_##ta##_eq(val1 + val2, val, \ "atomic_add should return new value"); \ assert_##ta##_eq(val1 + val2, raw_atomic, \ "atomic_add should update atomic"); \ \ /* Sub. */ \ raw_atomic = val1; \ val = atomic_sub_##ta(&raw_atomic, val2); \ assert_##ta##_eq(val1 - val2, val, \ "atomic_sub should return new value"); \ assert_##ta##_eq(val1 - val2, raw_atomic, \ "atomic_add should update atomic"); \ } while (0) #define TEST_STRUCT(t, ta) \ typedef struct { \ t val1; \ t val2; \ t val3; \ } ta##_test_t; #define TEST_CASES(t) { \ {(t)-1, (t)-1, (t)-2}, \ {(t)-1, (t) 0, (t)-2}, \ {(t)-1, (t) 1, (t)-2}, \ \ {(t) 0, (t)-1, (t)-2}, \ {(t) 0, (t) 0, (t)-2}, \ {(t) 0, (t) 1, (t)-2}, \ \ {(t) 1, (t)-1, (t)-2}, \ {(t) 1, (t) 0, (t)-2}, \ {(t) 1, (t) 1, (t)-2}, \ \ {(t)0, (t)-(1 << 22), (t)-2}, \ {(t)0, (t)(1 << 22), (t)-2}, \ {(t)(1 << 22), (t)-(1 << 22), (t)-2}, \ {(t)(1 << 22), (t)(1 << 22), (t)-2} \ } #define TEST_BODY(t, ta) do { \ const ta##_test_t tests[] = TEST_CASES(t); \ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \ ta##_test_t test = tests[i]; \ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \ } \ } while (0) #define INTEGER_TEST_BODY(t, ta) do { \ const ta##_test_t tests[] = TEST_CASES(t); \ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \ ta##_test_t test = tests[i]; \ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \ DO_INTEGER_TESTS(t, ta, test.val1, test.val2); \ } \ } while (0) TEST_STRUCT(uint64_t, u64); TEST_BEGIN(test_atomic_u64) { #if !(LG_SIZEOF_PTR == 3 || LG_SIZEOF_INT == 3) test_skip("64-bit atomic operations not supported"); #else INTEGER_TEST_BODY(uint64_t, u64); #endif } TEST_END TEST_STRUCT(uint32_t, u32); TEST_BEGIN(test_atomic_u32) { INTEGER_TEST_BODY(uint32_t, u32); } TEST_END TEST_STRUCT(void *, p); TEST_BEGIN(test_atomic_p) { TEST_BODY(void *, p); } TEST_END TEST_STRUCT(size_t, zu); TEST_BEGIN(test_atomic_zu) { INTEGER_TEST_BODY(size_t, zu); } TEST_END TEST_STRUCT(unsigned, u); TEST_BEGIN(test_atomic_u) { INTEGER_TEST_BODY(unsigned, u); } TEST_END int main(void) { return test( test_atomic_u64, test_atomic_u32, test_atomic_p, test_atomic_zu, test_atomic_u); }