Skip to content

Commit 35326b6

Browse files
m-ronnblomdavid-marchand
authored andcommitted
bitops: add atomic bit operations in new API
Add atomic bit test/set/clear/assign/flip and test-and-set/clear/assign/flip functions. All atomic bit functions allow (and indeed, require) the caller to specify a memory order. Bugzilla ID: 1385 Signed-off-by: Mattias Rönnblom <[email protected]> Acked-by: Morten Brørup <[email protected]> Acked-by: Tyler Retzlaff <[email protected]> Acked-by: Jack Bond-Preston <[email protected]>
1 parent 471de10 commit 35326b6

File tree

3 files changed

+641
-1
lines changed

3 files changed

+641
-1
lines changed

app/test/test_bitops.c

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
* Copyright(c) 2024 Ericsson AB
44
*/
55

6+
#include <inttypes.h>
67
#include <stdbool.h>
78

8-
#include <rte_launch.h>
99
#include <rte_bitops.h>
10+
#include <rte_cycles.h>
11+
#include <rte_launch.h>
12+
#include <rte_lcore.h>
1013
#include <rte_random.h>
1114
#include "test.h"
1215

@@ -53,6 +56,221 @@ GEN_TEST_BIT_ACCESS(test_bit_access32, rte_bit_set, rte_bit_clear, rte_bit_assig
5356
GEN_TEST_BIT_ACCESS(test_bit_access64, rte_bit_set, rte_bit_clear, rte_bit_assign, rte_bit_flip,
5457
rte_bit_test, 64)
5558

59+
#define bit_atomic_set(addr, nr) \
60+
rte_bit_atomic_set(addr, nr, rte_memory_order_relaxed)
61+
62+
#define bit_atomic_clear(addr, nr) \
63+
rte_bit_atomic_clear(addr, nr, rte_memory_order_relaxed)
64+
65+
#define bit_atomic_assign(addr, nr, value) \
66+
rte_bit_atomic_assign(addr, nr, value, rte_memory_order_relaxed)
67+
68+
#define bit_atomic_flip(addr, nr) \
69+
rte_bit_atomic_flip(addr, nr, rte_memory_order_relaxed)
70+
71+
#define bit_atomic_test(addr, nr) \
72+
rte_bit_atomic_test(addr, nr, rte_memory_order_relaxed)
73+
74+
GEN_TEST_BIT_ACCESS(test_bit_atomic_access32, bit_atomic_set, bit_atomic_clear, bit_atomic_assign,
75+
bit_atomic_flip, bit_atomic_test, 32)
76+
77+
GEN_TEST_BIT_ACCESS(test_bit_atomic_access64, bit_atomic_set, bit_atomic_clear, bit_atomic_assign,
78+
bit_atomic_flip, bit_atomic_test, 64)
79+
80+
#define PARALLEL_TEST_RUNTIME 0.25
81+
82+
#define GEN_TEST_BIT_PARALLEL_ASSIGN(size) \
83+
struct parallel_access_lcore ## size \
84+
{ \
85+
unsigned int bit; \
86+
uint ## size ##_t *word; \
87+
bool failed; \
88+
}; \
89+
static int \
90+
run_parallel_assign ## size(void *arg) \
91+
{ \
92+
struct parallel_access_lcore ## size *lcore = arg; \
93+
uint64_t deadline = rte_get_timer_cycles() + PARALLEL_TEST_RUNTIME * rte_get_timer_hz(); \
94+
bool value = false; \
95+
do { \
96+
bool new_value = rte_rand() & 1; \
97+
bool use_test_and_modify = rte_rand() & 1; \
98+
bool use_assign = rte_rand() & 1; \
99+
if (rte_bit_atomic_test(lcore->word, lcore->bit, \
100+
rte_memory_order_relaxed) != value) { \
101+
lcore->failed = true; \
102+
break; \
103+
} \
104+
if (use_test_and_modify) { \
105+
bool old_value; \
106+
if (use_assign) { \
107+
old_value = rte_bit_atomic_test_and_assign(lcore->word, \
108+
lcore->bit, new_value, rte_memory_order_relaxed); \
109+
} else { \
110+
old_value = new_value ? \
111+
rte_bit_atomic_test_and_set(lcore->word, lcore->bit, \
112+
rte_memory_order_relaxed) : \
113+
rte_bit_atomic_test_and_clear(lcore->word, lcore->bit, \
114+
rte_memory_order_relaxed); \
115+
} \
116+
if (old_value != value) { \
117+
lcore->failed = true; \
118+
break; \
119+
} \
120+
} else { \
121+
if (use_assign) { \
122+
rte_bit_atomic_assign(lcore->word, lcore->bit, new_value, \
123+
rte_memory_order_relaxed); \
124+
} else { \
125+
if (new_value) \
126+
rte_bit_atomic_set(lcore->word, lcore->bit, \
127+
rte_memory_order_relaxed); \
128+
else \
129+
rte_bit_atomic_clear(lcore->word, lcore->bit, \
130+
rte_memory_order_relaxed); \
131+
} \
132+
} \
133+
value = new_value; \
134+
} while (rte_get_timer_cycles() < deadline); \
135+
return 0; \
136+
} \
137+
static int \
138+
test_bit_atomic_parallel_assign ## size(void) \
139+
{ \
140+
unsigned int worker_lcore_id; \
141+
uint ## size ## _t word = 0; \
142+
struct parallel_access_lcore ## size lmain = { .word = &word }; \
143+
struct parallel_access_lcore ## size lworker = { .word = &word }; \
144+
if (rte_lcore_count() < 2) { \
145+
printf("Need multiple cores to run parallel test.\n"); \
146+
return TEST_SKIPPED; \
147+
} \
148+
worker_lcore_id = rte_get_next_lcore(-1, 1, 0); \
149+
lmain.bit = rte_rand_max(size); \
150+
do { \
151+
lworker.bit = rte_rand_max(size); \
152+
} while (lworker.bit == lmain.bit); \
153+
int rc = rte_eal_remote_launch(run_parallel_assign ## size, &lworker, worker_lcore_id); \
154+
TEST_ASSERT(rc == 0, "Worker thread launch failed"); \
155+
run_parallel_assign ## size(&lmain); \
156+
rte_eal_mp_wait_lcore(); \
157+
TEST_ASSERT(!lmain.failed, "Main lcore atomic access failed"); \
158+
TEST_ASSERT(!lworker.failed, "Worker lcore atomic access failed"); \
159+
return TEST_SUCCESS; \
160+
}
161+
162+
GEN_TEST_BIT_PARALLEL_ASSIGN(32)
163+
GEN_TEST_BIT_PARALLEL_ASSIGN(64)
164+
165+
#define GEN_TEST_BIT_PARALLEL_TEST_AND_MODIFY(size) \
166+
struct parallel_test_and_set_lcore ## size \
167+
{ \
168+
uint ## size ##_t *word; \
169+
unsigned int bit; \
170+
uint64_t flips; \
171+
}; \
172+
static int \
173+
run_parallel_test_and_modify ## size(void *arg) \
174+
{ \
175+
struct parallel_test_and_set_lcore ## size *lcore = arg; \
176+
uint64_t deadline = rte_get_timer_cycles() + PARALLEL_TEST_RUNTIME * rte_get_timer_hz(); \
177+
do { \
178+
bool old_value; \
179+
bool new_value = rte_rand() & 1; \
180+
bool use_assign = rte_rand() & 1; \
181+
if (use_assign) \
182+
old_value = rte_bit_atomic_test_and_assign(lcore->word, lcore->bit, \
183+
new_value, rte_memory_order_relaxed); \
184+
else \
185+
old_value = new_value ? \
186+
rte_bit_atomic_test_and_set(lcore->word, lcore->bit, \
187+
rte_memory_order_relaxed) : \
188+
rte_bit_atomic_test_and_clear(lcore->word, lcore->bit, \
189+
rte_memory_order_relaxed); \
190+
if (old_value != new_value) \
191+
lcore->flips++; \
192+
} while (rte_get_timer_cycles() < deadline); \
193+
return 0; \
194+
} \
195+
static int \
196+
test_bit_atomic_parallel_test_and_modify ## size(void) \
197+
{ \
198+
unsigned int worker_lcore_id; \
199+
uint ## size ## _t word = 0; \
200+
unsigned int bit = rte_rand_max(size); \
201+
struct parallel_test_and_set_lcore ## size lmain = { .word = &word, .bit = bit }; \
202+
struct parallel_test_and_set_lcore ## size lworker = { .word = &word, .bit = bit }; \
203+
if (rte_lcore_count() < 2) { \
204+
printf("Need multiple cores to run parallel test.\n"); \
205+
return TEST_SKIPPED; \
206+
} \
207+
worker_lcore_id = rte_get_next_lcore(-1, 1, 0); \
208+
int rc = rte_eal_remote_launch(run_parallel_test_and_modify ## size, &lworker, \
209+
worker_lcore_id); \
210+
TEST_ASSERT(rc == 0, "Worker thread launch failed"); \
211+
run_parallel_test_and_modify ## size(&lmain); \
212+
rte_eal_mp_wait_lcore(); \
213+
uint64_t total_flips = lmain.flips + lworker.flips; \
214+
bool expected_value = total_flips % 2; \
215+
TEST_ASSERT(expected_value == rte_bit_test(&word, bit), \
216+
"After %"PRId64" flips, the bit value should be %d", total_flips, expected_value); \
217+
uint64_t expected_word = 0; \
218+
rte_bit_assign(&expected_word, bit, expected_value); \
219+
TEST_ASSERT(expected_word == word, "Untouched bits have changed value"); \
220+
return TEST_SUCCESS; \
221+
}
222+
223+
GEN_TEST_BIT_PARALLEL_TEST_AND_MODIFY(32)
224+
GEN_TEST_BIT_PARALLEL_TEST_AND_MODIFY(64)
225+
226+
#define GEN_TEST_BIT_PARALLEL_FLIP(size) \
227+
struct parallel_flip_lcore ## size \
228+
{ \
229+
uint ## size ##_t *word; \
230+
unsigned int bit; \
231+
uint64_t flips; \
232+
}; \
233+
static int \
234+
run_parallel_flip ## size(void *arg) \
235+
{ \
236+
struct parallel_flip_lcore ## size *lcore = arg; \
237+
uint64_t deadline = rte_get_timer_cycles() + PARALLEL_TEST_RUNTIME * rte_get_timer_hz(); \
238+
do { \
239+
rte_bit_atomic_flip(lcore->word, lcore->bit, rte_memory_order_relaxed); \
240+
lcore->flips++; \
241+
} while (rte_get_timer_cycles() < deadline); \
242+
return 0; \
243+
} \
244+
static int \
245+
test_bit_atomic_parallel_flip ## size(void) \
246+
{ \
247+
unsigned int worker_lcore_id; \
248+
uint ## size ## _t word = 0; \
249+
unsigned int bit = rte_rand_max(size); \
250+
struct parallel_flip_lcore ## size lmain = { .word = &word, .bit = bit }; \
251+
struct parallel_flip_lcore ## size lworker = { .word = &word, .bit = bit }; \
252+
if (rte_lcore_count() < 2) { \
253+
printf("Need multiple cores to run parallel test.\n"); \
254+
return TEST_SKIPPED; \
255+
} \
256+
worker_lcore_id = rte_get_next_lcore(-1, 1, 0); \
257+
int rc = rte_eal_remote_launch(run_parallel_flip ## size, &lworker, worker_lcore_id); \
258+
TEST_ASSERT(rc == 0, "Worker thread launch failed"); \
259+
run_parallel_flip ## size(&lmain); \
260+
rte_eal_mp_wait_lcore(); \
261+
uint64_t total_flips = lmain.flips + lworker.flips; \
262+
bool expected_value = total_flips % 2; \
263+
TEST_ASSERT(expected_value == rte_bit_test(&word, bit), \
264+
"After %"PRId64" flips, the bit value should be %d", total_flips, expected_value); \
265+
uint64_t expected_word = 0; \
266+
rte_bit_assign(&expected_word, bit, expected_value); \
267+
TEST_ASSERT(expected_word == word, "Untouched bits have changed value"); \
268+
return TEST_SUCCESS; \
269+
}
270+
271+
GEN_TEST_BIT_PARALLEL_FLIP(32)
272+
GEN_TEST_BIT_PARALLEL_FLIP(64)
273+
56274
static uint32_t val32;
57275
static uint64_t val64;
58276

@@ -169,6 +387,16 @@ static struct unit_test_suite test_suite = {
169387
.unit_test_cases = {
170388
TEST_CASE(test_bit_access32),
171389
TEST_CASE(test_bit_access64),
390+
TEST_CASE(test_bit_access32),
391+
TEST_CASE(test_bit_access64),
392+
TEST_CASE(test_bit_atomic_access32),
393+
TEST_CASE(test_bit_atomic_access64),
394+
TEST_CASE(test_bit_atomic_parallel_assign32),
395+
TEST_CASE(test_bit_atomic_parallel_assign64),
396+
TEST_CASE(test_bit_atomic_parallel_test_and_modify32),
397+
TEST_CASE(test_bit_atomic_parallel_test_and_modify64),
398+
TEST_CASE(test_bit_atomic_parallel_flip32),
399+
TEST_CASE(test_bit_atomic_parallel_flip64),
172400
TEST_CASE(test_bit_relaxed_set),
173401
TEST_CASE(test_bit_relaxed_clear),
174402
TEST_CASE(test_bit_relaxed_test_set_clear),

doc/guides/rel_notes/release_24_11.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ New Features
6464
performance (by avoiding restricting the compiler and CPU), but give
6565
no guarantees in regards to memory ordering or atomicity.
6666

67+
* ``rte_bit_atomic_*`` provide atomic bit-level operations, including
68+
the possibility to specify memory ordering constraints.
69+
6770
The new public API elements are polymorphic, using the _Generic-based
6871
macros (for C) and function overloading (in C++ translation units).
6972

0 commit comments

Comments
 (0)