PatchworkOS  19e446b
A non-POSIX operating system.
Loading...
Searching...
No Matches
ref.h
Go to the documentation of this file.
1#pragma once
2
3#include <kernel/sync/lock.h>
4
5#include <sys/defs.h>
6#include <sys/list.h>
7
8#include <assert.h>
9#include <stdatomic.h>
10#include <stdint.h>
11
12typedef struct ref ref_t;
13
14/**
15 * @brief Reference counting with weak pointers.
16 * @defgroup kernel_utils_ref Reference counting
17 * @ingroup kernel_utils
18 *
19 * @{
20 */
21
22/**
23 * @brief Magic value used in debug builds to check for corruption or invalid use of the `ref_t` structure.
24 */
25#define REF_MAGIC 0x26CB6E4C
26
27/**
28 * @brief Weak pointer structure.
29 * @struct weak_ptr_t
30 *
31 * Used to hold a non-owning reference to an object. If all strong references to the object are released, the weak
32 * pointer will be set to `NULL` and an optional callback will be invoked.
33 */
34typedef struct weak_ptr
35{
38 void (*callback)(void* arg);
39 void* arg;
42
43/**
44 * @brief Reference counting structure
45 * @struct ref_t
46 *
47 * Provides a generic interface for reference counting. Must be placed as the first element in any struct that requires
48 * reference counting.
49 *
50 */
51typedef struct ref
52{
53#ifndef NDEBUG
55#endif
56 atomic_uint32_t count;
58 void (*callback)(void* self); ///< Cleanup function called when count reaches zero
60} ref_t;
61
62/**
63 * @brief Get current reference count.
64 *
65 * Primarily intended to be used with RCU protected objects to check if they are still alive within a RCU read critical
66 * section.
67 *
68 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
69 * @return Current reference count, or `0` if `ptr` is `NULL`.
70 */
71#define REF_COUNT(ptr) ((ptr) == NULL ? 0 : atomic_load_explicit(&((ref_t*)(ptr))->count, memory_order_relaxed))
72
73/**
74 * @brief Increment reference count
75 *
76 * Atomically increments the reference counter. Used to avoid the need for a typecast. The magic number checking makes
77 * sure we cant accidentally misuse this.
78 *
79 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
80 * @return The `ptr` passed as input
81 */
82#define REF(ptr) \
83 ({ \
84 ref_t* ref = (ref_t*)(ptr); \
85 ref_inc(ref); \
86 ptr; \
87 })
88
89/**
90 * @brief Increment reference count, but only if the current count is not zero.
91 *
92 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
93 * @return The `ptr` passed as input, or `NULL` if the count was zero.
94 */
95#define REF_TRY(ptr) \
96 ({ \
97 ref_t* ref = (ref_t*)(ptr); \
98 ref_inc_try(ref) != NULL ? ptr : NULL; \
99 })
100
101/**
102 * @brief Decrement reference count
103 *
104 * Atomically decrements the reference counter. Used to avoid the need for a typecast. The magic number checking makes
105 * sure we cant accidentally misuse this.
106 *
107 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
108 */
109#define UNREF(ptr) \
110 ({ \
111 ref_t* ref = (ref_t*)(ptr); \
112 ref_dec(ref); \
113 })
114
115/**
116 * @brief RAII-style cleanup for scoped references
117 *
118 * Uses GCC's cleanup attribute to automatically call `ref_dec` when going out of scope.
119 *
120 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
121 */
122#define UNREF_DEFER(ptr) __attribute__((cleanup(ref_defer_cleanup))) void* CONCAT(p, __COUNTER__) = (ptr)
123
124/**
125 * @brief Initialize a reference counter
126 *
127 * @param ref Pointer to the reference counter structure
128 * @param callback Callback to call when count reaches zero
129 */
130static inline void ref_init(ref_t* ref, void* callback)
131{
132#ifndef NDEBUG
133 ref->magic = REF_MAGIC;
134#endif
135 atomic_init(&ref->count, 1);
136 ref->callback = callback;
137 list_init(&ref->weakRefs);
138 lock_init(&ref->lock);
139}
140
141/**
142 * @brief Increment reference count
143 *
144 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
145 * @return The `ptr` passed as input
146 */
147static inline void* ref_inc(void* ptr)
148{
149 ref_t* ref = (ref_t*)ptr;
150 if (ref == NULL)
151 {
152 return NULL;
153 }
154
155 assert(ref->magic == REF_MAGIC);
157 return ptr;
158}
159
160/**
161 * @brief Increment reference count, but only if the current count is not zero.
162 *
163 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
164 * @return The `ptr` passed as input, or `NULL` if the count was zero.
165 */
166static inline void* ref_inc_try(void* ptr)
167{
168 ref_t* ref = (ref_t*)ptr;
169 if (ref == NULL)
170 {
171 return NULL;
172 }
173
174 assert(ref->magic == REF_MAGIC);
176 do
177 {
178 if (count == 0)
179 {
180 return NULL;
181 }
184
185 return ptr;
186}
187
188/**
189 * @brief Decrement reference count
190 *
191 * If count reaches zero it calls the registered cleanup function.
192 *
193 * @param ptr Pointer to the struct containing `ref_t` as its first member, can be `NULL`.
194 */
195static inline void ref_dec(void* ptr)
196{
197 ref_t* ref = (ref_t*)ptr;
198 if (ref == NULL)
199 {
200 return;
201 }
202
203 assert(ref->magic == REF_MAGIC);
205 if (count > 1)
206 {
207 return;
208 }
209
211 assert(count == 1); // Count is now zero, if it was zero before then we have a double free.
212
213 lock_acquire(&ref->lock);
214
215 weak_ptr_t* wp;
216 LIST_FOR_EACH(wp, &ref->weakRefs, entry)
217 {
218 lock_acquire(&wp->lock);
219 if (wp->callback)
220 {
221 wp->callback(wp->arg);
222 }
223 wp->ref = NULL;
224 lock_release(&wp->lock);
225 }
226
227 lock_release(&ref->lock);
228
229 if (ref->callback == NULL)
230 {
231 return;
232 }
233
234 ref->callback(ptr);
235}
236
237static inline void ref_defer_cleanup(void** ptr)
238{
239 ref_dec(*ptr);
240}
241
242/**
243 * @brief Set a weak pointer.
244 *
245 * The provided callback must not attempt to access the weak ptr as that would cause a deadlock.
246 *
247 * @param wp Pointer to the weak pointer structure
248 * @param ref Pointer to the reference counting structure to point to, can be `NULL`.
249 * @param callback Callback function to call when the strong reference count reaches zero.
250 * @param arg Argument to pass to the callback function.
251 */
252static inline void weak_ptr_set(weak_ptr_t* wp, ref_t* ref, void (*callback)(void*), void* arg)
253{
254 lock_init(&wp->lock);
255 if (ref == NULL)
256 {
257 wp->ref = NULL;
259 wp->callback = NULL;
260 wp->arg = NULL;
261 return;
262 }
263 wp->ref = ref;
264 assert(wp->ref->magic == REF_MAGIC);
266 wp->callback = callback;
267 wp->arg = arg;
268
269 LOCK_SCOPE(&wp->ref->lock);
270 list_push_back(&wp->ref->weakRefs, &wp->entry);
271}
272
273/**
274 * @brief Clear a weak pointer.
275 *
276 * Will not invoke the callback.
277 *
278 * @param wp Pointer to the weak pointer structure.
279 */
280static inline void weak_ptr_clear(weak_ptr_t* wp)
281{
282 while (true)
283 {
284 lock_acquire(&wp->lock);
285 ref_t* ref = wp->ref;
286 if (ref == NULL)
287 {
288 lock_release(&wp->lock);
289 return;
290 }
291
292 if (lock_try_acquire(&ref->lock))
293 {
294 list_remove(&wp->entry);
295 wp->ref = NULL;
296 wp->callback = NULL;
297 wp->arg = NULL;
298 lock_release(&ref->lock);
299 lock_release(&wp->lock);
300 return;
301 }
302
303 lock_release(&wp->lock);
304 ASM("pause");
305 }
306}
307
308/**
309 * @brief Upgrade a weak pointer to a strong pointer.
310 *
311 * If the strong reference count is zero, `NULL` is returned.
312 *
313 * @param wp Pointer to the weak pointer structure
314 * @return On success, pointer to the struct containing `ref_t` as its first member. On failure, `NULL`.
315 */
316static inline void* weak_ptr_get(weak_ptr_t* wp)
317{
318 lock_acquire(&wp->lock);
319 ref_t* ref = wp->ref;
320 if (ref == NULL)
321 {
322 lock_release(&wp->lock);
323 return NULL;
324 }
325
326 if (ref_inc_try(ref) == NULL)
327 {
328 lock_release(&wp->lock);
329 return NULL;
330 }
331
332 lock_release(&wp->lock);
333 return ref;
334}
335
336/** @} */
#define assert(expression)
Definition assert.h:29
static void lock_init(lock_t *lock)
Initializes a lock.
Definition lock.h:79
static bool lock_try_acquire(lock_t *lock)
Tries to acquire a lock.
Definition lock.h:138
#define LOCK_SCOPE(lock)
Acquires a lock for the reminder of the current scope.
Definition lock.h:58
static void lock_release(lock_t *lock)
Releases a lock.
Definition lock.h:175
static void lock_acquire(lock_t *lock)
Acquires a lock, blocking until it is available.
Definition lock.h:96
static void ref_defer_cleanup(void **ptr)
Definition ref.h:237
static void * ref_inc_try(void *ptr)
Increment reference count, but only if the current count is not zero.
Definition ref.h:166
static void weak_ptr_set(weak_ptr_t *wp, ref_t *ref, void(*callback)(void *), void *arg)
Set a weak pointer.
Definition ref.h:252
static void weak_ptr_clear(weak_ptr_t *wp)
Clear a weak pointer.
Definition ref.h:280
static void ref_dec(void *ptr)
Decrement reference count.
Definition ref.h:195
static void * weak_ptr_get(weak_ptr_t *wp)
Upgrade a weak pointer to a strong pointer.
Definition ref.h:316
#define REF_MAGIC
Magic value used in debug builds to check for corruption or invalid use of the ref_t structure.
Definition ref.h:25
static void * ref_inc(void *ptr)
Increment reference count.
Definition ref.h:147
static void ref_init(ref_t *ref, void *callback)
Initialize a reference counter.
Definition ref.h:130
#define ASM(...)
Inline assembly macro.
Definition defs.h:160
#define LIST_FOR_EACH(elem, list, member)
Iterates over a list.
Definition list.h:58
static void list_remove(list_entry_t *entry)
Removes a list entry from its current list.
Definition list.h:290
static void list_push_back(list_t *list, list_entry_t *entry)
Pushes an entry to the end of the list.
Definition list.h:322
static void list_entry_init(list_entry_t *entry)
Initializes a list entry.
Definition list.h:173
static void list_init(list_t *list)
Initializes a list.
Definition list.h:185
#define NULL
Pointer error value.
Definition NULL.h:25
static atomic_long count
Definition main.c:11
@ memory_order_release
Definition stdatomic.h:119
@ memory_order_relaxed
Definition stdatomic.h:116
@ memory_order_acquire
Definition stdatomic.h:118
#define atomic_fetch_add_explicit(object, operand, order)
Definition stdatomic.h:259
#define atomic_load_explicit(object, order)
Definition stdatomic.h:264
#define atomic_thread_fence(order)
Definition stdatomic.h:135
#define atomic_compare_exchange_weak_explicit(object, expected, desired, success, failure)
Definition stdatomic.h:240
#define atomic_init(obj, value)
Definition stdatomic.h:75
#define atomic_fetch_sub_explicit(object, operand, order)
Definition stdatomic.h:262
__UINT32_TYPE__ uint32_t
Definition stdint.h:15
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
A entry in a doubly linked list.
Definition list.h:37
A doubly linked list.
Definition list.h:46
A simple ticket lock implementation.
Definition lock.h:44
Reference counting structure.
Definition ref.h:52
atomic_uint32_t count
Definition ref.h:56
void(* callback)(void *self)
Cleanup function called when count reaches zero.
Definition ref.h:58
uint32_t magic
Definition ref.h:54
list_t weakRefs
Definition ref.h:59
lock_t lock
Definition ref.h:57
Weak pointer structure.
Definition ref.h:35
lock_t lock
Definition ref.h:40
void * arg
Definition ref.h:39
void(* callback)(void *arg)
Definition ref.h:38
ref_t * ref
Definition ref.h:36
list_entry_t entry
Definition ref.h:37