// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause /* * Copyright (c) 2025-2026 Meta Platforms, Inc. and affiliates. * Copyright (c) 2025-2026 Emil Tsalapatis */ #include #include #include #include static inline u64 spmc_arr_size(volatile struct spmc_arr __arena *spmc_arr) { return SPMC_ARR_BASESZ << spmc_arr->order; } static inline u64 spmc_arr_get(volatile struct spmc_arr __arena *spmc_arr, u64 ind) { u64 ret = READ_ONCE(spmc_arr->data[ind % spmc_arr_size(spmc_arr)]); return ret; } static inline void spmc_arr_put(volatile struct spmc_arr __arena *spmc_arr, u64 ind, u64 value) { WRITE_ONCE(spmc_arr->data[ind % spmc_arr_size(spmc_arr)], value); } static inline void spmc_arr_copy(volatile struct spmc_arr __arena *dst, volatile struct spmc_arr __arena *src, u64 b, u64 t) { u64 i; for (i = t; i < b && can_loop; i++) spmc_arr_put(dst, i, spmc_arr_get(src, i)); } static inline int spmc_order_init(struct spmc __arena *spmc, int order) { volatile struct spmc_arr __arena *arr = &spmc->arr[order]; if (unlikely(!spmc)) return -EINVAL; if (order >= SPMC_ARR_ORDERS) return -E2BIG; /* Already allocated? */ if (arr->data) return 0; arr->data = arena_malloc((SPMC_ARR_BASESZ << order) * sizeof(*arr->data)); if (!arr->data) return -ENOMEM; return 0; } __weak int spmc_owned_add(struct spmc __arena *spmc, u64 val) { volatile struct spmc_arr __arena *newarr; volatile struct spmc_arr __arena *arr; ssize_t sz; u64 b, t; int ret; if (unlikely(!spmc)) return -EINVAL; /* * Bottom must always be read first, also * see spmc_steal(). */ b = smp_load_acquire(&spmc->bottom); t = READ_ONCE(spmc->top); arr = READ_ONCE(spmc->cur); sz = b - t; if (sz >= spmc_arr_size(arr) - 1) { ret = spmc_order_init(spmc, arr->order + 1); if (ret) return ret; newarr = &spmc->arr[arr->order + 1]; spmc_arr_copy(newarr, arr, b, t); smp_store_release(&spmc->cur, newarr); arr = newarr; } spmc_arr_put(arr, b, val); smp_store_release(&spmc->bottom, b + 1); return 0; } __weak int spmc_owned_remove(struct spmc __arena *spmc, u64 *val) { volatile struct spmc_arr __arena *arr; int ret = 0; ssize_t sz; u64 value; u64 b, t; if (unlikely(!spmc || !val)) return -EINVAL; b = READ_ONCE(spmc->bottom) - 1; WRITE_ONCE(spmc->bottom, b); smp_mb(); t = READ_ONCE(spmc->top); arr = READ_ONCE(spmc->cur); sz = b - t; if (sz < 0) { WRITE_ONCE(spmc->bottom, t); return -ENOENT; } value = spmc_arr_get(arr, b); if (sz > 0) { *val = value; return 0; } if (cmpxchg(&spmc->top, t, t + 1) != t) ret = -EAGAIN; WRITE_ONCE(spmc->bottom, t + 1); if (ret) return ret; *val = value; return 0; } __weak int spmc_steal(struct spmc __arena *spmc, u64 *val) { volatile struct spmc_arr __arena *arr; ssize_t sz; u64 value; u64 b, t; if (unlikely(!spmc || !val)) return -EINVAL; /* * It is important that t is read before b for * stealers to avoid racing with the owner. * Races between stealers are dealt with using * CAS to increment the top value below. */ t = smp_load_acquire(&spmc->top); b = smp_load_acquire(&spmc->bottom); sz = b - t; if (sz <= 0) return -ENOENT; arr = smp_load_acquire(&spmc->cur); value = spmc_arr_get(arr, t); if (cmpxchg(&spmc->top, t, t + 1) != t) return -EAGAIN; *val = value; return 0; } __weak struct spmc __arena *spmc_create(void) { /* * Marked as volatile because otherwise the array * reference in the internal loop gets demoted to * scalar and the program fails verification. */ struct spmc __arena *volatile spmc; int ret, i; spmc = arena_malloc(sizeof(*spmc)); if (!spmc) return NULL; spmc->bottom = 0; spmc->top = 0; for (i = 0; i < SPMC_ARR_ORDERS && can_loop; i++) { spmc->arr[i].data = NULL; spmc->arr[i].order = i; } ret = spmc_order_init((struct spmc __arena *)spmc, 0); if (ret) { arena_free(spmc); return NULL; } spmc->cur = &spmc->arr[0]; return (struct spmc __arena *)spmc; } __weak int spmc_destroy(struct spmc __arena *spmc) { int i; if (unlikely(!spmc)) return -EINVAL; for (i = 0; i < SPMC_ARR_ORDERS && can_loop; i++) arena_free(spmc->arr[i].data); arena_free(spmc); return 0; }