1#include <threads.h>
2
3#include <assert.h>
4
5#include "futex_impl.h"
6
7enum {
8    STATE_INIT = 0, // we're the first; run init
9    STATE_WAIT = 1, // another thread is running init; wait
10    STATE_DONE = 2, // another thread finished running init; just return
11    STATE_WAKE = 3, // another thread is running init, waiters present; wait
12};
13
14static_assert(STATE_INIT == ONCE_FLAG_INIT, "");
15
16static void once_full(once_flag* control, void (*init)(void)) {
17    for (;;)
18        switch (a_cas_shim(control, STATE_INIT, STATE_WAIT)) {
19        case STATE_INIT:
20            init();
21
22            if (atomic_exchange(control, STATE_DONE) == STATE_WAKE)
23                __wake(control, -1);
24            return;
25        case STATE_WAIT:
26            /* If this fails, so will __wait. */
27            a_cas_shim(control, STATE_WAIT, STATE_WAKE);
28        case STATE_WAKE:
29            __wait(control, NULL, STATE_WAKE);
30            continue;
31        case STATE_DONE:
32            return;
33        }
34}
35
36void call_once(once_flag* control, void (*init)(void)) {
37    /* Return immediately if init finished before, but ensure that
38     * effects of the init routine are visible to the caller. */
39    if (atomic_load(control) == STATE_DONE) {
40        atomic_thread_fence(memory_order_seq_cst);
41        return;
42    }
43    once_full(control, init);
44}
45