Skip to content

Commit 388d590

Browse files
committed
Add seqlock implementation
1 parent 60b5355 commit 388d590

File tree

6 files changed

+241
-1
lines changed

6 files changed

+241
-1
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ purpose of these programs is to be illustrative and educational.
2929
- [hashmap](hashmap/): A concurrent hashmap implementation.
3030
- [lf-timer](lf-timer/): A lock-free timer.
3131
* [Synchronization](https://en.wikipedia.org/wiki/Synchronization_(computer_science))
32-
- [mcslock](mcslock/): MCS lock implementation.
32+
- [mcslock](mcslock/): An MCS lock implementation.
33+
- [seqlock](seqlock/): A seqlock (sequence lock) implementation.
3334
- [hp\_list](hp_list)/: A concurrent linked list utilizing Hazard Pointers.
3435
- [rcu-list](rcu-list/): A concurrent linked list utilizing the simplified RCU algorithm.
3536
- [qsbr](qsbr/): An implementation of Quiescent state based reclamation (QSBR).

seqlock/Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
all:
2+
gcc -o tests -std=gnu11 -Wall -O2 seqlock.c tests.c
3+
4+
clean:
5+
rm -f tests

seqlock/expect.h

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
#define EX_HASHSTR(s) #s
7+
#define EX_STR(s) EX_HASHSTR(s)
8+
9+
#define EXPECT(exp) \
10+
{ \
11+
if (!(exp)) \
12+
fprintf(stderr, "FAILURE @ %s:%u; %s\n", __FILE__, __LINE__, \
13+
EX_STR(exp)), \
14+
abort(); \
15+
}

seqlock/seqlock.c

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include <assert.h>
2+
#include <stdbool.h>
3+
#include <stdint.h>
4+
#include <stdio.h>
5+
#include <string.h>
6+
7+
#include "seqlock.h"
8+
9+
#define SEQLOCK_WRITER 1U
10+
11+
#if defined(__i386__) || defined(__x86_64__)
12+
#define spin_wait() __builtin_ia32_pause()
13+
#elif defined(__aarch64__)
14+
#define spin_wait() __asm__ __volatile__("isb\n")
15+
#else
16+
#define spin_wait() ((void) 0)
17+
#endif
18+
19+
#if defined(__aarch64__)
20+
#define SEVL() sevl()
21+
static inline void sevl(void)
22+
{
23+
__asm__ volatile("sevl" : : :);
24+
}
25+
#define WFE() wfe()
26+
static inline int wfe(void)
27+
{
28+
__asm__ volatile("wfe" : : : "memory");
29+
return 1;
30+
}
31+
#define LDX(a, b) ldx((a), (b))
32+
static inline uint32_t ldx(const uint8_t *var, int mm)
33+
{
34+
uint32_t old;
35+
if (mm == __ATOMIC_ACQUIRE)
36+
__asm volatile("ldaxrb %w0, [%1]" : "=&r"(old) : "r"(var) : "memory");
37+
else if (mm == __ATOMIC_RELAXED)
38+
__asm volatile("ldxrb %w0, [%1]" : "=&r"(old) : "r"(var) : "memory");
39+
else
40+
abort();
41+
return old;
42+
}
43+
#else /* generic */
44+
#define SEVL() (void) 0
45+
#define WFE() 1
46+
#define LDX(a, b) __atomic_load_n((a), (b))
47+
#endif
48+
49+
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
50+
51+
void seqlock_init(seqlock_t *sync)
52+
{
53+
*sync = 0;
54+
}
55+
56+
static inline seqlock_t wait_for_no_writer(const seqlock_t *sync, int mo)
57+
{
58+
seqlock_t l;
59+
SEVL(); /* Do SEVL early to avoid excessive loop alignment (NOPs) */
60+
if (UNLIKELY(((l = __atomic_load_n(sync, mo)) & SEQLOCK_WRITER) != 0)) {
61+
while (WFE() && ((l = LDX(sync, mo)) & SEQLOCK_WRITER) != 0)
62+
spin_wait();
63+
}
64+
assert((l & SEQLOCK_WRITER) == 0); /* No writer in progress */
65+
return l;
66+
}
67+
68+
seqlock_t seqlock_acquire_rd(const seqlock_t *sync)
69+
{
70+
/* Wait for any present writer to go away */
71+
/* B: Synchronize with A */
72+
return wait_for_no_writer(sync, __ATOMIC_ACQUIRE);
73+
}
74+
75+
bool seqlock_release_rd(const seqlock_t *sync, seqlock_t prv)
76+
{
77+
/* Enforce Load/Load order as if synchronizing with a store-release or
78+
* fence-release in another thread.
79+
*/
80+
__atomic_thread_fence(__ATOMIC_ACQUIRE);
81+
/* Test if sync remains unchanged => success */
82+
return __atomic_load_n(sync, __ATOMIC_RELAXED) == prv;
83+
}
84+
85+
void seqlock_acquire_wr(seqlock_t *sync)
86+
{
87+
seqlock_t l;
88+
do {
89+
/* Wait for any present writer to go away */
90+
l = wait_for_no_writer(sync, __ATOMIC_RELAXED);
91+
/* Attempt to increment, setting writer flag */
92+
} while (
93+
/* C: Synchronize with A */
94+
!__atomic_compare_exchange_n(sync, &l, l + SEQLOCK_WRITER,
95+
/*weak=*/true, __ATOMIC_ACQUIRE,
96+
__ATOMIC_RELAXED));
97+
/* Enforce Store/Store order as if synchronizing with a load-acquire or
98+
* fence-acquire in another thread.
99+
*/
100+
__atomic_thread_fence(__ATOMIC_RELEASE);
101+
}
102+
103+
void seqlock_release_wr(seqlock_t *sync)
104+
{
105+
seqlock_t cur = *sync;
106+
if (UNLIKELY(cur & SEQLOCK_WRITER) == 0) {
107+
perror("seqlock: invalid write release");
108+
return;
109+
}
110+
111+
/* Increment, clearing writer flag */
112+
/* A: Synchronize with B and C */
113+
__atomic_store_n(sync, cur + 1, __ATOMIC_RELEASE);
114+
}
115+
116+
#define ATOMIC_COPY(_d, _s, _sz, _type) \
117+
({ \
118+
_type val = __atomic_load_n((const _type *) (_s), __ATOMIC_RELAXED); \
119+
_s += sizeof(_type); \
120+
__atomic_store_n((_type *) (_d), val, __ATOMIC_RELAXED); \
121+
_d += sizeof(_type); \
122+
_sz -= sizeof(_type); \
123+
})
124+
125+
static inline void atomic_memcpy(char *dst, const char *src, size_t sz)
126+
{
127+
#if __SIZEOF_POINTER__ == 8
128+
while (sz >= sizeof(uint64_t))
129+
ATOMIC_COPY(dst, src, sz, uint64_t);
130+
if (sz >= sizeof(uint32_t))
131+
ATOMIC_COPY(dst, src, sz, uint32_t);
132+
#else //__SIZEOF_POINTER__ == 4
133+
while (sz >= sizeof(uint32_t))
134+
ATOMIC_COPY(dst, src, sz, uint32_t);
135+
#endif
136+
if (sz >= sizeof(uint16_t))
137+
ATOMIC_COPY(dst, src, sz, uint16_t);
138+
if (sz >= sizeof(uint8_t))
139+
ATOMIC_COPY(dst, src, sz, uint8_t);
140+
}
141+
142+
void seqlock_read(seqlock_t *sync, void *dst, const void *data, size_t len)
143+
{
144+
seqlock_t prv;
145+
do {
146+
prv = seqlock_acquire_rd(sync);
147+
atomic_memcpy(dst, data, len);
148+
} while (!seqlock_release_rd(sync, prv));
149+
}
150+
151+
void seqlock_write(seqlock_t *sync, const void *src, void *data, size_t len)
152+
{
153+
seqlock_acquire_wr(sync);
154+
atomic_memcpy(data, src, len);
155+
seqlock_release_wr(sync);
156+
}

seqlock/seqlock.h

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#pragma once
2+
3+
#include <stdbool.h>
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
typedef uint32_t seqlock_t;
8+
9+
/* Initialise a seqlock aka reader/writer synchronization */
10+
void seqlock_init(seqlock_t *sync);
11+
12+
/* Acquire a seqlock for reading
13+
* Block until no write is in progress
14+
*/
15+
seqlock_t seqlock_acquire_rd(const seqlock_t *sync);
16+
17+
/* Release a read seqlock
18+
* Return false if a write has occurred or is in progress
19+
* This means any read data may be inconsistent and the operation should be
20+
* restarted
21+
*/
22+
bool seqlock_release_rd(const seqlock_t *sync, seqlock_t prv);
23+
24+
/* Acquire a seqlock for writing
25+
* Block until earlier writes have completed
26+
*/
27+
void seqlock_acquire_wr(seqlock_t *sync);
28+
29+
/* Release a write seqlock */
30+
void seqlock_release_wr(seqlock_t *sync);
31+
32+
/* Perform an atomic read of the associated data
33+
* Will block for concurrent writes
34+
*/
35+
void seqlock_read(seqlock_t *sync, void *dst, const void *data, size_t len);
36+
37+
/* Perform an atomic write of the associated data
38+
* Will block for concurrent writes
39+
*/
40+
void seqlock_write(seqlock_t *sync, const void *src, void *data, size_t len);

seqlock/tests.c

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
4+
#include "expect.h"
5+
#include "seqlock.h"
6+
7+
int main(void)
8+
{
9+
seqlock_t sync;
10+
seqlock_t s;
11+
char data[24] = {0};
12+
data[23] = (char) 255;
13+
seqlock_init(&sync);
14+
s = seqlock_acquire_rd(&sync);
15+
EXPECT(seqlock_release_rd(&sync, s) == true);
16+
s = seqlock_acquire_rd(&sync);
17+
seqlock_write(&sync, "Mary had a little lamb", data, 23);
18+
EXPECT(seqlock_release_rd(&sync, s) == false);
19+
EXPECT(strncmp(data, "Mary had a little lamb", 23) == 0);
20+
EXPECT(data[23] == (char) 255);
21+
22+
return 0;
23+
}

0 commit comments

Comments
 (0)