Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/ck/src/ck_ec.c
48261 views
1
#include <ck_ec.h>
2
#include <ck_limits.h>
3
4
#include "ck_ec_timeutil.h"
5
6
#define DEFAULT_BUSY_LOOP_ITER 100U
7
8
/*
9
* The 2ms, 8x/iter default parameter hit 1.024 seconds after 3
10
* iterations.
11
*/
12
#define DEFAULT_INITIAL_WAIT_NS 2000000L /* Start at 2 ms */
13
/* Grow the wait time 8x/iteration. */
14
#define DEFAULT_WAIT_SCALE_FACTOR 8
15
#define DEFAULT_WAIT_SHIFT_COUNT 0
16
17
struct ck_ec32_slow_path_state {
18
struct ck_ec32 *ec;
19
uint32_t flagged_word;
20
};
21
22
#ifdef CK_F_EC64
23
struct ck_ec64_slow_path_state {
24
struct ck_ec64 *ec;
25
uint64_t flagged_word;
26
};
27
#endif
28
29
/* Once we've waited for >= 1 sec, go for the full deadline. */
30
static const struct timespec final_wait_time = {
31
.tv_sec = 1
32
};
33
34
void
35
ck_ec32_wake(struct ck_ec32 *ec, const struct ck_ec_ops *ops)
36
{
37
/* Spurious wake-ups are OK. Clear the flag before futexing. */
38
ck_pr_and_32(&ec->counter, (1U << 31) - 1);
39
ops->wake32(ops, &ec->counter);
40
return;
41
}
42
43
int
44
ck_ec32_wait_slow(struct ck_ec32 *ec,
45
const struct ck_ec_ops *ops,
46
uint32_t old_value,
47
const struct timespec *deadline)
48
{
49
return ck_ec32_wait_pred_slow(ec, ops, old_value,
50
NULL, NULL, deadline);
51
}
52
53
#ifdef CK_F_EC64
54
void
55
ck_ec64_wake(struct ck_ec64 *ec, const struct ck_ec_ops *ops)
56
{
57
ck_pr_and_64(&ec->counter, ~1);
58
ops->wake64(ops, &ec->counter);
59
return;
60
}
61
62
int
63
ck_ec64_wait_slow(struct ck_ec64 *ec,
64
const struct ck_ec_ops *ops,
65
uint64_t old_value,
66
const struct timespec *deadline)
67
{
68
return ck_ec64_wait_pred_slow(ec, ops, old_value,
69
NULL, NULL, deadline);
70
}
71
#endif
72
73
int
74
ck_ec_deadline_impl(struct timespec *new_deadline,
75
const struct ck_ec_ops *ops,
76
const struct timespec *timeout)
77
{
78
struct timespec now;
79
int r;
80
81
if (timeout == NULL) {
82
new_deadline->tv_sec = TIME_MAX;
83
new_deadline->tv_nsec = NSEC_MAX;
84
return 0;
85
}
86
87
r = ops->gettime(ops, &now);
88
if (r != 0) {
89
return -1;
90
}
91
92
*new_deadline = timespec_add(now, *timeout);
93
return 0;
94
}
95
96
/* The rest of the file implements wait_pred_slow. */
97
98
/*
99
* Returns a timespec value for deadline_ptr. If deadline_ptr is NULL,
100
* returns a timespec far in the future.
101
*/
102
static struct timespec
103
canonical_deadline(const struct timespec *deadline_ptr)
104
{
105
106
if (deadline_ptr == NULL) {
107
return (struct timespec) { .tv_sec = TIME_MAX };
108
}
109
110
return *deadline_ptr;
111
}
112
113
/*
114
* Really slow (sleeping) path for ck_ec_wait. Drives the exponential
115
* backoff scheme to sleep for longer and longer periods of time,
116
* until either the sleep function returns true (the eventcount's
117
* value has changed), or the predicate returns non-0 (something else
118
* has changed).
119
*
120
* If deadline is ever reached, returns -1 (timeout).
121
*
122
* TODO: add some form of randomisation to the intermediate timeout
123
* values.
124
*/
125
static int
126
exponential_backoff(struct ck_ec_wait_state *wait_state,
127
bool (*sleep)(const void *sleep_state,
128
const struct ck_ec_wait_state *wait_state,
129
const struct timespec *partial_deadline),
130
const void *sleep_state,
131
int (*pred)(const struct ck_ec_wait_state *state,
132
struct timespec *deadline),
133
const struct timespec *deadline)
134
{
135
struct timespec begin;
136
struct timespec stop_backoff;
137
const struct ck_ec_ops *ops = wait_state->ops;
138
const uint32_t scale_factor = (ops->wait_scale_factor != 0)
139
? ops->wait_scale_factor
140
: DEFAULT_WAIT_SCALE_FACTOR;
141
const uint32_t shift_count = (ops->wait_shift_count != 0)
142
? ops->wait_shift_count
143
: DEFAULT_WAIT_SHIFT_COUNT;
144
uint32_t wait_ns = (ops->initial_wait_ns != 0)
145
? ops->initial_wait_ns
146
: DEFAULT_INITIAL_WAIT_NS;
147
bool first = true;
148
149
for (;;) {
150
struct timespec now;
151
struct timespec partial_deadline;
152
153
if (check_deadline(&now, ops, *deadline) == true) {
154
/* Timeout. Bail out. */
155
return -1;
156
}
157
158
if (first) {
159
begin = now;
160
wait_state->start = begin;
161
stop_backoff = timespec_add(begin, final_wait_time);
162
first = false;
163
}
164
165
wait_state->now = now;
166
if (timespec_cmp(now, stop_backoff) >= 0) {
167
partial_deadline = *deadline;
168
} else {
169
do {
170
partial_deadline =
171
timespec_add_ns(begin, wait_ns);
172
wait_ns =
173
wait_time_scale(wait_ns,
174
scale_factor,
175
shift_count);
176
} while (timespec_cmp(partial_deadline, now) <= 0);
177
}
178
179
if (pred != NULL) {
180
int r = pred(wait_state, &partial_deadline);
181
if (r != 0) {
182
return r;
183
}
184
}
185
186
/* Canonicalize deadlines in the far future to NULL. */
187
if (sleep(sleep_state, wait_state,
188
((partial_deadline.tv_sec == TIME_MAX)
189
? NULL : &partial_deadline)) == true) {
190
return 0;
191
}
192
}
193
}
194
195
/*
196
* Loops up to BUSY_LOOP_ITER times, or until ec's counter value
197
* (including the flag) differs from old_value.
198
*
199
* Returns the new value in ec.
200
*/
201
#define DEF_WAIT_EASY(W) \
202
static uint##W##_t ck_ec##W##_wait_easy(struct ck_ec##W* ec, \
203
const struct ck_ec_ops *ops, \
204
uint##W##_t expected) \
205
{ \
206
uint##W##_t current = ck_pr_load_##W(&ec->counter); \
207
size_t n = (ops->busy_loop_iter != 0) \
208
? ops->busy_loop_iter \
209
: DEFAULT_BUSY_LOOP_ITER; \
210
\
211
for (size_t i = 0; \
212
i < n && current == expected; \
213
i++) { \
214
ck_pr_stall(); \
215
current = ck_pr_load_##W(&ec->counter); \
216
} \
217
\
218
return current; \
219
}
220
221
DEF_WAIT_EASY(32)
222
#ifdef CK_F_EC64
223
DEF_WAIT_EASY(64)
224
#endif
225
#undef DEF_WAIT_EASY
226
/*
227
* Attempts to upgrade ec->counter from unflagged to flagged.
228
*
229
* Returns true if the event count has changed. Otherwise, ec's
230
* counter word is equal to flagged on return, or has been at some
231
* time before the return.
232
*/
233
#define DEF_UPGRADE(W) \
234
static bool ck_ec##W##_upgrade(struct ck_ec##W* ec, \
235
uint##W##_t current, \
236
uint##W##_t unflagged, \
237
uint##W##_t flagged) \
238
{ \
239
uint##W##_t old_word; \
240
\
241
if (current == flagged) { \
242
/* Nothing to do, no change. */ \
243
return false; \
244
} \
245
\
246
if (current != unflagged) { \
247
/* We have a different counter value! */ \
248
return true; \
249
} \
250
\
251
/* \
252
* Flag the counter value. The CAS only fails if the \
253
* counter is already flagged, or has a new value. \
254
*/ \
255
return (ck_pr_cas_##W##_value(&ec->counter, \
256
unflagged, flagged, \
257
&old_word) == false && \
258
old_word != flagged); \
259
}
260
261
DEF_UPGRADE(32)
262
#ifdef CK_F_EC64
263
DEF_UPGRADE(64)
264
#endif
265
#undef DEF_UPGRADE
266
267
/*
268
* Blocks until partial_deadline on the ck_ec. Returns true if the
269
* eventcount's value has changed. If partial_deadline is NULL, wait
270
* forever.
271
*/
272
static bool
273
ck_ec32_wait_slow_once(const void *vstate,
274
const struct ck_ec_wait_state *wait_state,
275
const struct timespec *partial_deadline)
276
{
277
const struct ck_ec32_slow_path_state *state = vstate;
278
const struct ck_ec32 *ec = state->ec;
279
const uint32_t flagged_word = state->flagged_word;
280
281
wait_state->ops->wait32(wait_state, &ec->counter,
282
flagged_word, partial_deadline);
283
return ck_pr_load_32(&ec->counter) != flagged_word;
284
}
285
286
#ifdef CK_F_EC64
287
static bool
288
ck_ec64_wait_slow_once(const void *vstate,
289
const struct ck_ec_wait_state *wait_state,
290
const struct timespec *partial_deadline)
291
{
292
const struct ck_ec64_slow_path_state *state = vstate;
293
const struct ck_ec64 *ec = state->ec;
294
const uint64_t flagged_word = state->flagged_word;
295
296
/* futex_wait will only compare the low 32 bits. Perform a
297
* full comparison here to maximise the changes of catching an
298
* ABA in the low 32 bits.
299
*/
300
if (ck_pr_load_64(&ec->counter) != flagged_word) {
301
return true;
302
}
303
304
wait_state->ops->wait64(wait_state, &ec->counter,
305
flagged_word, partial_deadline);
306
return ck_pr_load_64(&ec->counter) != flagged_word;
307
}
308
#endif
309
310
/*
311
* The full wait logic is a lot of code (> 1KB). Encourage the
312
* compiler to lay this all out linearly with LIKELY annotations on
313
* every early exit.
314
*/
315
#define WAIT_SLOW_BODY(W, ec, ops, pred, data, deadline_ptr, \
316
old_value, unflagged, flagged) \
317
do { \
318
struct ck_ec_wait_state wait_state = { \
319
.ops = ops, \
320
.data = data \
321
}; \
322
const struct ck_ec##W##_slow_path_state state = { \
323
.ec = ec, \
324
.flagged_word = flagged \
325
}; \
326
const struct timespec deadline = \
327
canonical_deadline(deadline_ptr); \
328
\
329
/* Detect infinite past deadlines. */ \
330
if (CK_CC_LIKELY(deadline.tv_sec <= 0)) { \
331
return -1; \
332
} \
333
\
334
for (;;) { \
335
uint##W##_t current; \
336
int r; \
337
\
338
current = ck_ec##W##_wait_easy(ec, ops, unflagged); \
339
\
340
/* \
341
* We're about to wait harder (i.e., \
342
* potentially with futex). Make sure the \
343
* counter word is flagged. \
344
*/ \
345
if (CK_CC_LIKELY( \
346
ck_ec##W##_upgrade(ec, current, \
347
unflagged, flagged) == true)) { \
348
ck_pr_fence_acquire(); \
349
return 0; \
350
} \
351
\
352
/* \
353
* By now, ec->counter == flagged_word (at \
354
* some point in the past). Spin some more to \
355
* heuristically let any in-flight SP inc/add \
356
* to retire. This does not affect \
357
* correctness, but practically eliminates \
358
* lost wake-ups. \
359
*/ \
360
current = ck_ec##W##_wait_easy(ec, ops, flagged); \
361
if (CK_CC_LIKELY(current != flagged_word)) { \
362
ck_pr_fence_acquire(); \
363
return 0; \
364
} \
365
\
366
r = exponential_backoff(&wait_state, \
367
ck_ec##W##_wait_slow_once, \
368
&state, \
369
pred, &deadline); \
370
if (r != 0) { \
371
return r; \
372
} \
373
\
374
if (ck_ec##W##_value(ec) != old_value) { \
375
ck_pr_fence_acquire(); \
376
return 0; \
377
} \
378
\
379
/* Spurious wake-up. Redo the slow path. */ \
380
} \
381
} while (0)
382
383
int
384
ck_ec32_wait_pred_slow(struct ck_ec32 *ec,
385
const struct ck_ec_ops *ops,
386
uint32_t old_value,
387
int (*pred)(const struct ck_ec_wait_state *state,
388
struct timespec *deadline),
389
void *data,
390
const struct timespec *deadline_ptr)
391
{
392
const uint32_t unflagged_word = old_value;
393
const uint32_t flagged_word = old_value | (1UL << 31);
394
395
if (CK_CC_UNLIKELY(ck_ec32_value(ec) != old_value)) {
396
return 0;
397
}
398
399
WAIT_SLOW_BODY(32, ec, ops, pred, data, deadline_ptr,
400
old_value, unflagged_word, flagged_word);
401
}
402
403
#ifdef CK_F_EC64
404
int
405
ck_ec64_wait_pred_slow(struct ck_ec64 *ec,
406
const struct ck_ec_ops *ops,
407
uint64_t old_value,
408
int (*pred)(const struct ck_ec_wait_state *state,
409
struct timespec *deadline),
410
void *data,
411
const struct timespec *deadline_ptr)
412
{
413
const uint64_t unflagged_word = old_value << 1;
414
const uint64_t flagged_word = unflagged_word | 1;
415
416
if (CK_CC_UNLIKELY(ck_ec64_value(ec) != old_value)) {
417
return 0;
418
}
419
420
WAIT_SLOW_BODY(64, ec, ops, pred, data, deadline_ptr,
421
old_value, unflagged_word, flagged_word);
422
}
423
#endif
424
425
#undef WAIT_SLOW_BODY
426
427