···11#include <stdatomic.h>
22+#include <limits.h>
33+#include <stdbool.h>
2435#include "lock.h"
46#include "simple.h"
5768#ifndef XTRACE_LOCK_DEBUG
77- #define XTRACE_LOCK_DEBUG 0
99+ #define XTRACE_LOCK_DEBUG 1
810#endif
9111012#if XTRACE_LOCK_DEBUG
1111- #define xtrace_lock_debug(x, ...) __simple_printf(x "\n", ## __VA_ARGS__)
1313+ #define xtrace_lock_debug_internal(x, ...) __simple_printf(x "\n", ## __VA_ARGS__)
1214 #undef XTRACE_INLINE
1315 #define XTRACE_INLINE
1416#else
1515- #define xtrace_lock_debug(x, ...)
1717+ #define xtrace_lock_debug_internal(x, ...)
1618#endif
17192020+#define xtrace_lock_debug(x, ...) xtrace_lock_debug_internal("lock %p: " x, lock, ## __VA_ARGS__)
2121+#define xtrace_once_debug(x, ...) xtrace_lock_debug_internal("once %p: " x, once, ## __VA_ARGS__)
2222+2323+//
2424+// xtrace_lock
2525+//
1826// simple lock implementation on top of futexes, based on https://github.com/eliben/code-for-blog/blob/master/2018/futex-basics/mutex-using-futex.cpp
2727+//
19282029// used to hide the use of C11 atomics from external code (e.g. C++ code, which can't use C11 atomics).
2130// it's *technically* undefined behavior to cast the external one to this one, but that's okay;
···2433 _Atomic uint32_t state;
2534} xtrace_lock_internal_t;
26352727-typedef struct xtrace_once_internal {
2828- _Atomic uint8_t state;
2929-} xtrace_once_internal_t;
3030-3136enum xtrace_lock_state {
3237 // the lock is unlocked (duh)
3338 xtrace_lock_state_unlocked = 0,
···3944 xtrace_lock_state_locked_contended = 2,
4045};
41464242-enum xtrace_once_state {
4343- // the once hasn't been run yet
4444- xtrace_once_state_untriggered = 0,
4545-4646- // the once has already been run at least once
4747- xtrace_once_state_triggered = 1,
4747+XTRACE_INLINE
4848+uint32_t cmpxchg_wrapper_u32(_Atomic uint32_t* atom, uint32_t expected, uint32_t desired) {
4949+ atomic_compare_exchange_strong_explicit(atom, &expected, desired, memory_order_acq_rel, memory_order_acquire);
5050+ return expected;
4851};
49525053void xtrace_lock_lock(xtrace_lock_t* _lock) {
5154 xtrace_lock_internal_t* lock = (xtrace_lock_internal_t*)_lock;
52555353- xtrace_lock_debug("lock %p: locking", lock);
5656+ xtrace_lock_debug("locking");
54575558 // we do the initial exchange using `xtrace_lock_state_locked_uncontended` because
5659 // if it was previously unlocked, it is now locked but nobody is waiting for it
5757- uint32_t prev = atomic_exchange(&lock->state, xtrace_lock_state_locked_uncontended);
6060+ uint32_t prev = cmpxchg_wrapper_u32(&lock->state, xtrace_lock_state_unlocked, xtrace_lock_state_locked_uncontended);
58615959- xtrace_lock_debug("lock %p: previous state was %u", lock, prev);
6262+ xtrace_lock_debug("previous state was %u", prev);
60636164 // if it was not unlocked, we need to do some waiting
6265 if (prev != xtrace_lock_state_unlocked) {
6366 do {
6464- // we can skip the atomic_exchange if the lock was already contended
6565- // also, when we do the atomic_exchange, we need to make sure it's still locked
6767+ // we can skip the cmpxchg if the lock was already contended
6868+ // also, when we do the cmpxchg, we need to make sure it's still locked
6669 // we use `xtrace_lock_state_locked_contended` because it was either uncontended (but it is now) or it was already contended
6767- if (prev == xtrace_lock_state_locked_contended || atomic_exchange(&lock->state, xtrace_lock_state_locked_contended) != xtrace_lock_state_unlocked) {
6868- xtrace_lock_debug("lock %p: going to wait", lock);
7070+ if (prev == xtrace_lock_state_locked_contended || cmpxchg_wrapper_u32(&lock->state, xtrace_lock_state_locked_uncontended, xtrace_lock_state_locked_contended) != xtrace_lock_state_unlocked) {
7171+ xtrace_lock_debug("going to wait");
6972 // do the actual sleeping
7073 // we expect `xtrace_lock_state_locked_contended` because we don't want to sleep if it's not contended
7174 __linux_futex(&lock->state, FUTEX_WAIT, xtrace_lock_state_locked_contended, NULL, 0, 0);
7272- xtrace_lock_debug("lock %p: awoken", lock);
7575+ xtrace_lock_debug("awoken");
7376 }
74777578 // loop as long as the lock was not unlocked
7679 // we use `xtrace_lock_state_locked_contended` for the same reason as before
7777- } while ((prev = atomic_exchange(&lock->state, xtrace_lock_state_locked_contended)) != xtrace_lock_state_unlocked);
7878- xtrace_lock_debug("lock %p: lock acquired after sleeping", lock);
8080+ } while ((prev = cmpxchg_wrapper_u32(&lock->state, xtrace_lock_state_unlocked, xtrace_lock_state_locked_contended)) != xtrace_lock_state_unlocked);
8181+ xtrace_lock_debug("lock acquired after sleeping");
7982 }
80838181- xtrace_lock_debug("lock %p: lock acquired", lock);
8484+ xtrace_lock_debug("lock acquired");
8285};
83868487void xtrace_lock_unlock(xtrace_lock_t* _lock) {
8588 xtrace_lock_internal_t* lock = (xtrace_lock_internal_t*)_lock;
86898787- xtrace_lock_debug("lock %p: unlocking", lock);
9090+ xtrace_lock_debug("unlocking");
88918989- uint32_t prev = atomic_exchange(&lock->state, xtrace_lock_state_unlocked);
9292+ uint32_t prev = atomic_exchange_explicit(&lock->state, xtrace_lock_state_unlocked, memory_order_acq_rel);
90939191- xtrace_lock_debug("lock %p: previous state was %u", prev);
9494+ xtrace_lock_debug("previous state was %u", prev);
92959396 // if it was previously contended, then we need to wake someone up
9497 if (prev == xtrace_lock_state_locked_contended) {
9595- xtrace_lock_debug("lock %p: waking someone up", lock);
9898+ xtrace_lock_debug("waking someone up");
9699 __linux_futex(&lock->state, FUTEX_WAKE, 1, NULL, 0, 0);
97100 }
98101};
99102103103+//
104104+// xtrace_once
105105+//
106106+// see https://github.com/bugaevc/serenity/blob/1fc3f2ba84d9c75c6c30e62014dabe4fcd62aae1/Libraries/LibPthread/pthread_once.cpp
107107+//
108108+109109+typedef struct xtrace_once_internal {
110110+ _Atomic uint32_t state;
111111+} xtrace_once_internal_t;
112112+113113+enum xtrace_once_state {
114114+ // the once hasn't been triggered yet
115115+ xtrace_once_state_untriggered = 0,
116116+117117+ // the once has been triggered, but no one is waiting for it
118118+ xtrace_once_state_triggered_uncontended = 1,
119119+120120+ // the once has been triggered and there are waiters
121121+ xtrace_once_state_triggered_contended = 2,
122122+123123+ // the once has been triggered and has completed execution
124124+ xtrace_once_state_completed = 3,
125125+};
126126+100127void xtrace_once(xtrace_once_t* _once, xtrace_once_callback callback) {
101101- xtrace_lock_internal_t* once = (xtrace_lock_internal_t*)_once;
102102- if (atomic_exchange(&once->state, xtrace_once_state_triggered) == xtrace_once_state_untriggered) {
128128+ xtrace_once_internal_t* once = (xtrace_once_internal_t*)_once;
129129+130130+ xtrace_once_debug("evaluating...");
131131+132132+ uint32_t prev = xtrace_once_state_untriggered;
133133+ bool exchanged = atomic_compare_exchange_strong_explicit(&once->state, &prev, xtrace_once_state_triggered_uncontended, memory_order_acq_rel, memory_order_acquire);
134134+135135+ xtrace_once_debug("previous state was %u", prev);
136136+137137+ if (exchanged) {
138138+ xtrace_once_debug("performing callback...");
139139+140140+ // we had `xtrace_once_state_untriggered` and now we have `xtrace_once_state_triggered_uncontended`, so let's do the callback
103141 callback();
142142+143143+ xtrace_once_debug("callback done; updating state...");
144144+145145+ // now let's update the state to tell everyone we're done
146146+ prev = atomic_exchange_explicit(&once->state, xtrace_once_state_completed, memory_order_acq_rel);
147147+148148+ xtrace_once_debug("previous state was %u", prev);
149149+150150+ switch (prev) {
151151+ // no one was waiting, so we have no one to wake up
152152+ case xtrace_once_state_triggered_uncontended: break;
153153+ case xtrace_once_state_triggered_contended: {
154154+ xtrace_once_debug("waking up all waiters...");
155155+ // otherwise, we have to wake someone up
156156+ __linux_futex(&once->state, FUTEX_WAKE, INT_MAX, NULL, 0, 0);
157157+ } break;
158158+ }
159159+160160+ return;
161161+ }
162162+163163+ xtrace_once_debug("someone else is performing the callback");
164164+165165+ while (true) {
166166+ switch (prev) {
167167+ case xtrace_once_state_completed: {
168168+ // we're done
169169+ xtrace_once_debug("callback completed by someone else; returning...");
170170+ return;
171171+ }; // not reached
172172+ case xtrace_once_state_triggered_uncontended: {
173173+ // somebody is already perfoming the callback;
174174+ // now we're waiting, so let's update the state for that
175175+ xtrace_once_debug("someone is already performing the callback with no waiters; becoming first waiter...");
176176+ exchanged = atomic_compare_exchange_strong_explicit(&once->state, &prev, xtrace_once_state_triggered_contended, memory_order_acq_rel, memory_order_acquire);
177177+ if (!exchanged) {
178178+ xtrace_once_debug("state changed from uncontended; re-evaluating...");
179179+ // something changed, let's re-evaluate
180180+ continue;
181181+ }
182182+ prev = xtrace_once_state_triggered_contended;
183183+ }; // fall through
184184+ case xtrace_once_state_triggered_contended: {
185185+ // somebody is already performing the callback and there are already waiters;
186186+ // let's wait
187187+ xtrace_once_debug("someone is already performing the callback with waiters; going to wait...");
188188+ __linux_futex(&once->state, FUTEX_WAIT, prev, NULL, 0, 0);
189189+190190+ xtrace_once_debug("woken up");
191191+192192+ // we got woken up, but that may have been spurious;
193193+ // let's loop again to re-evaluate
194194+ prev = atomic_load_explicit(&once->state, memory_order_acquire);
195195+ xtrace_once_debug("previous state was %u; going to re-evaluate...", prev);
196196+ } break;
197197+ }
104198 }
105199};