summaryrefslogtreecommitdiff
path: root/test/unit/atomic.c
blob: 572d8d23fe139a50d54fd74049cfb1c08cf88538 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#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 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");	\
	}								\
									\
									\
} while (0)

#define DO_INTEGER_TESTS(t, ta, val1, val2) do {			\
	atomic_##ta##_t atom;						\
	t val;								\
									\
	/* 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");				\
} 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(ssize_t, zd);
TEST_BEGIN(test_atomic_zd) {
	INTEGER_TEST_BODY(ssize_t, zd);
}
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_zd,
	    test_atomic_u);
}