ae2f_docs
c89atomic_deque.c
1#ifndef c89atomic_deque_c
2#define c89atomic_deque_c
3
5
6/* BEG c89atomic_deque.c */
7C89ATOMIC_DEQUE_API void c89atomic_deque_init(c89atomic_deque* pDeque)
8{
9 pDeque->head = 0;
10 pDeque->tail = 0;
11}
12
13C89ATOMIC_DEQUE_API c89atomic_deque_result c89atomic_deque_push_tail(c89atomic_deque* pDeque, C89ATOMIC_DEQUE_T value)
14{
15 c89atomic_uint32 tail;
16 c89atomic_uint32 head;
17
18 tail = c89atomic_load_explicit_32(&pDeque->tail, c89atomic_memory_order_relaxed);
19 head = c89atomic_load_explicit_32(&pDeque->head, c89atomic_memory_order_acquire);
20
21 if ((tail - head) >= C89ATOMIC_DEQUE_CAP) {
22 return C89ATOMIC_DEQUE_OUT_OF_MEMORY; /* Queue is full */
23 }
24
25 c89atomic_store_explicit_ptr((volatile void**)&pDeque->buffer[tail & (C89ATOMIC_DEQUE_CAP - 1)], value, c89atomic_memory_order_relaxed);
26 c89atomic_thread_fence(c89atomic_memory_order_release);
27 c89atomic_store_explicit_32(&pDeque->tail, tail + 1, c89atomic_memory_order_relaxed);
28
29 return C89ATOMIC_DEQUE_SUCCESS;
30}
31
32C89ATOMIC_DEQUE_API c89atomic_deque_result c89atomic_deque_take_tail(c89atomic_deque* pDeque, C89ATOMIC_DEQUE_T* pValue)
33{
34 c89atomic_uint32 tail;
35 c89atomic_uint32 head;
36
37 tail = c89atomic_load_explicit_32(&pDeque->tail, c89atomic_memory_order_relaxed);
38 tail -= 1UL; /* Can underflow, but should not matter here because we'll be masking with `C89ATOMIC_DEQUE_CAP`, and the conditional below is cast to a signed integer. See note below. */
39 c89atomic_store_explicit_32(&pDeque->tail, tail, c89atomic_memory_order_relaxed);
40 c89atomic_thread_fence(c89atomic_memory_order_seq_cst);
41 head = c89atomic_load_explicit_32(&pDeque->head, c89atomic_memory_order_relaxed);
42
43 /*
44 NOTE:
45
46 The LĂȘ et al. paper has an unsigned integer underflow bug in it. Just above we are loading the tail and the
47 subtracting 1 from it. When a deque is freshly initialized, most developers will intuitively set the head
48 and tail to 0. As a result, the subtraction by 1 will result in the tail underflowing. In the paper, the
49 conditional below is done against the unsigned values of the head and tail which will result in the
50 algorithm thinking that the deque is filled when it's actually empty.
51
52 I'm going to fix this by casting to a signed integer.
53 */
54 if ((c89atomic_int32)head <= (c89atomic_int32)tail) {
55 /* Not empty. */
57
58 x = c89atomic_load_explicit_ptr((volatile void**)&pDeque->buffer[tail & (C89ATOMIC_DEQUE_CAP - 1)], c89atomic_memory_order_relaxed);
59 if (head == tail) {
60 /* Last item in queue. In this case the head is moved forward rather than moving the tail backward. */
61 c89atomic_bool raceResult = c89atomic_compare_exchange_strong_explicit_32(&pDeque->head, &head, head + 1, c89atomic_memory_order_seq_cst, c89atomic_memory_order_relaxed);
62
63 /*
64 Regardless of the output of the race we need to put the tail back to it's original location. In this branch (when we are taking
65 the last item in the queue), we are moving the head forward rather than the tail backward which means we need to undo the subtraction
66 by 1 we did earlier.
67 */
68 c89atomic_store_explicit_32(&pDeque->tail, tail + 1, c89atomic_memory_order_relaxed);
69
70 /* Terminate early if we failed the race against a theif. */
71 if (!raceResult) {
72 return C89ATOMIC_DEQUE_NO_DATA_AVAILABLE;
73 }
74 }
75
76 *pValue = x;
77 return C89ATOMIC_DEQUE_SUCCESS;
78 } else {
79 /* Empty. */
80 c89atomic_store_explicit_32(&pDeque->tail, tail + 1, c89atomic_memory_order_relaxed);
81 return C89ATOMIC_DEQUE_NO_DATA_AVAILABLE;
82 }
83}
84
85C89ATOMIC_DEQUE_API c89atomic_deque_result c89atomic_deque_take_head(c89atomic_deque* pDeque, C89ATOMIC_DEQUE_T* pValue)
86{
87 c89atomic_uint32 head;
88 c89atomic_uint32 tail;
89
90 head = c89atomic_load_explicit_32(&pDeque->head, c89atomic_memory_order_acquire);
91 c89atomic_thread_fence(c89atomic_memory_order_seq_cst);
92 tail = c89atomic_load_explicit_32(&pDeque->tail, c89atomic_memory_order_acquire);
93
94 if ((c89atomic_int32)head < (c89atomic_int32)tail) {
96
97 x = c89atomic_load_explicit_ptr((volatile void**)&pDeque->buffer[head & (C89ATOMIC_DEQUE_CAP - 1)], c89atomic_memory_order_relaxed);
98 if (!c89atomic_compare_exchange_strong_explicit_32(&pDeque->head, &head, head + 1, c89atomic_memory_order_seq_cst, c89atomic_memory_order_relaxed)) {
99 return C89ATOMIC_DEQUE_CANCELLED; /* Failed race. */
100 }
101
102 *pValue = x;
103 return C89ATOMIC_DEQUE_SUCCESS;
104 } else {
105 return C89ATOMIC_DEQUE_NO_DATA_AVAILABLE; /* Empty. */
106 }
107}
108/* END c89atomic_deque.c */
109
110#endif /* c89atomic_deque_c */
#define c89atomic_memory_order_release
Definition c89atomic.h:583
#define c89atomic_memory_order_seq_cst
Definition c89atomic.h:585
#define c89atomic_memory_order_acquire
Definition c89atomic.h:582
#define c89atomic_memory_order_relaxed
Definition c89atomic.h:580
#define C89ATOMIC_DEQUE_T
#define C89ATOMIC_DEQUE_CAP
#define C89ATOMIC_DEQUE_API