blob: d1e6ad1432d39247ff5e050c6bd41294763522ff [file] [edit]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* BPF-side helpers for cids and cmasks. See kernel/sched/ext/cid.h for the
* authoritative layout and semantics. The BPF-side helpers use the cmask_*
* naming (no scx_ prefix); cmask is the SCX bitmap type so the prefix is
* redundant in BPF code. Every helper is inline except the binary operations,
* which are weak global functions defined here, so there is no .c counterpart.
*
* Included by scx/common.bpf.h; don't include directly.
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
* Copyright (c) 2026 Tejun Heo <tj@kernel.org>
*/
#ifndef __SCX_CID_BPF_H
#define __SCX_CID_BPF_H
#include "bpf_arena_common.bpf.h"
/* libbpf's bpf_helpers.h defines it from 1.4 on */
#ifndef __arg_arena
#define __arg_arena __attribute((btf_decl_tag("arg:arena")))
#endif
#ifndef BIT_U64
#define BIT_U64(nr) (1ULL << (nr))
#endif
#ifndef GENMASK_U64
#define GENMASK_U64(h, l) ((~0ULL << (l)) & (~0ULL >> (63 - (h))))
#endif
/*
* Storage cap for bounded loops over bits[]. Sized to cover NR_CPUS=8192 with
* one extra word for head-misalignment. Increase if deployment targets larger
* NR_CPUS.
*/
#ifndef CMASK_MAX_WORDS
#define CMASK_MAX_WORDS 129
#endif
/*
* Mirrors SCX_CMASK_NR_WORDS in kernel/sched/ext/types.h. The u64 cast keeps
* the +63 from wrapping when @nr_cids is near U32_MAX, so cmask_reframe()
* bounds-checking the result against alloc_words catches the overflow instead
* of seeing a small value.
*/
#define CMASK_NR_WORDS(nr_cids) ((u32)(((u64)(nr_cids) + 63) / 64 + 1))
static __always_inline bool __cmask_contains(u32 cid, const struct scx_cmask __arena *m)
{
return cid >= m->base && cid < m->base + m->nr_cids;
}
static __always_inline u64 __arena *__cmask_word(u32 cid, const struct scx_cmask __arena *m)
{
return (u64 __arena *)&m->bits[cid / 64 - m->base / 64];
}
/**
* __cmask_init - Initialize @m with explicit storage capacity
* @m: cmask to initialize
* @base: first cid of the active range
* @nr_cids: number of cids in the active range
* @alloc_cids: storage capacity in cids, at least @nr_cids
*
* Use when storage is sized larger than the initial active range. All of
* bits[] is zeroed.
*/
static __always_inline void __cmask_init(struct scx_cmask __arena *m, u32 base,
u32 nr_cids, u32 alloc_cids)
{
u32 alloc_words, i;
if (unlikely(nr_cids > alloc_cids)) {
scx_bpf_error("__cmask_init: nr_cids=%u exceeds alloc_cids=%u",
nr_cids, alloc_cids);
return;
}
alloc_words = CMASK_NR_WORDS(alloc_cids);
m->base = base;
m->nr_cids = nr_cids;
m->alloc_words = alloc_words;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= alloc_words)
break;
m->bits[i] = 0;
}
}
/**
* cmask_init - Initialize @m on tight storage
* @m: cmask to initialize
* @base: first cid of the active range
* @nr_cids: number of cids in the active range
*
* All of bits[] is zeroed.
*/
static __always_inline void cmask_init(struct scx_cmask __arena *m, u32 base, u32 nr_cids)
{
__cmask_init(m, base, nr_cids, nr_cids);
}
/**
* cmask_reframe - Reshape @m's active range without resizing storage
* @m: cmask to reframe
* @base: new active range base
* @nr_cids: new active range length, must fit within @m->alloc_words
*
* Body bits within the new range become garbage - only the head and tail
* words are zeroed to keep the padding invariant.
*/
static __always_inline void cmask_reframe(struct scx_cmask __arena *m, u32 base, u32 nr_cids)
{
if (CMASK_NR_WORDS(nr_cids) > m->alloc_words) {
scx_bpf_error("cmask_reframe: nr_cids=%u exceeds alloc_words=%u",
nr_cids, m->alloc_words);
return;
}
if (nr_cids) {
u32 last_word = ((base & 63) + nr_cids - 1) / 64;
m->bits[0] = 0;
m->bits[last_word] = 0;
}
m->base = base;
m->nr_cids = nr_cids;
}
/**
* cmask_nr_words - Words of bits[] that @m's active range spans
* @m: cmask to measure
*
* @m->base need not be word aligned: bits[0] covers the whole word @m->base
* falls in, so the count is taken from that word, not from @m->base.
*/
static __always_inline u32 cmask_nr_words(const struct scx_cmask __arena *m)
{
u32 wbase = m->base / 64;
return m->nr_cids ? (m->base + m->nr_cids - 1) / 64 - wbase + 1 : 0;
}
/**
* cmask_word - Read word @k of @m
* @m: cmask to read
* @k: word index, counted from the word @m->base falls in
*
* A word past the active range reads as 0, so a scan that looks one word
* ahead (at an SMT sibling that falls in the next word, say) needs no bound
* of its own.
*/
static __always_inline u64 cmask_word(const struct scx_cmask __arena *m, u32 k)
{
if (k >= cmask_nr_words(m))
return 0;
return m->bits[k];
}
/**
* cmask_range_word - Bits of word @k of @m that fall in [@start, @start + @nr)
* @m: cmask the word belongs to
* @k: word index, counted from the word @m->base falls in
* @start: first cid of the range
* @nr: number of cids in the range
*
* Scoping a scan to a domain that is contiguous in cid space, an LLC or a
* node, is then one AND per word, rather than a test of the ends of the
* range at every bit or a mask kept per domain.
*/
static __always_inline u64 cmask_range_word(const struct scx_cmask __arena *m,
u32 k, u32 start, u32 nr)
{
u64 wlo = (u64)(m->base / 64 + k) * 64, whi = wlo + 64;
u64 lo = start, hi = (u64)start + nr;
if (lo < wlo)
lo = wlo;
if (hi > whi)
hi = whi;
if (lo >= hi)
return 0;
return GENMASK_U64(hi - wlo - 1, lo - wlo);
}
/**
* __cmask_test - Test a cid without checking it against the active range
* @cid: cid to test
* @m: cmask to test
*
* The caller must already know that @cid lies within
* [@m->base, @m->base + @m->nr_cids), or the read runs off @m->bits.
*
* For hot scans that have already bounded @cid, where the per-call range check
* is repeated work on every candidate.
*/
static __always_inline bool __cmask_test(u32 cid, const struct scx_cmask __arena *m)
{
return *__cmask_word(cid, m) & BIT_U64(cid & 63);
}
static __always_inline bool cmask_test(u32 cid, const struct scx_cmask __arena *m)
{
if (!__cmask_contains(cid, m))
return false;
return __cmask_test(cid, m);
}
/*
* The bit helpers use the fetching bitwise builtins, which order like the
* cmpxchg they replace, and not every JIT accepts those on arena pointers. The
* loader sets SCX_LIB_FEAT_ARENA_FETCH_BITOPS when a probe with the fetching
* form loads, and the helpers run a cmpxchg loop otherwise. The fetched value
* is consumed through barrier_var() because clang 19 lowers a builtin whose
* result is unused to the non-fetching form, which is relaxed on arm64.
*
* CMASK_CAS_TRIES is sized so exhausting it means seconds of real spinning
* on one word, past any plausible contention. Abort hard.
*/
#define CMASK_CAS_TRIES (1U << 23)
static __always_inline u64 __cmask_fetch_or(u64 __arena *w, u64 mask)
{
u64 old = __atomic_fetch_or(w, mask, __ATOMIC_ACQ_REL);
barrier_var(old);
return old;
}
static __always_inline u64 __cmask_fetch_andnot(u64 __arena *w, u64 mask)
{
u64 old = __atomic_fetch_and(w, ~mask, __ATOMIC_ACQ_REL);
barrier_var(old);
return old;
}
static __always_inline void cmask_set(u32 cid, struct scx_cmask __arena *m)
{
u64 __arena *w;
u64 bit, old, new;
u32 i;
if (!__cmask_contains(cid, m))
return;
w = __cmask_word(cid, m);
bit = BIT_U64(cid & 63);
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
__cmask_fetch_or(w, bit);
return;
}
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if (old & bit)
return;
new = old | bit;
if (__sync_val_compare_and_swap(w, old, new) == old)
return;
}
scx_bpf_error("cmask_set CAS exhausted at cid %u", cid);
}
static __always_inline void cmask_clear(u32 cid, struct scx_cmask __arena *m)
{
u64 __arena *w;
u64 bit, old, new;
u32 i;
if (!__cmask_contains(cid, m))
return;
w = __cmask_word(cid, m);
bit = BIT_U64(cid & 63);
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
__cmask_fetch_andnot(w, bit);
return;
}
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if (!(old & bit))
return;
new = old & ~bit;
if (__sync_val_compare_and_swap(w, old, new) == old)
return;
}
scx_bpf_error("cmask_clear CAS exhausted at cid %u", cid);
}
static __always_inline bool cmask_test_and_set(u32 cid, struct scx_cmask __arena *m)
{
u64 __arena *w;
u64 bit, old, new;
u32 i;
if (!__cmask_contains(cid, m))
return false;
w = __cmask_word(cid, m);
bit = BIT_U64(cid & 63);
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS))
return __cmask_fetch_or(w, bit) & bit;
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if (old & bit)
return true;
new = old | bit;
if (__sync_val_compare_and_swap(w, old, new) == old)
return false;
}
scx_bpf_error("cmask_test_and_set CAS exhausted at cid %u", cid);
return false;
}
static __always_inline bool cmask_test_and_clear(u32 cid, struct scx_cmask __arena *m)
{
u64 __arena *w;
u64 bit, old, new;
u32 i;
if (!__cmask_contains(cid, m))
return false;
w = __cmask_word(cid, m);
bit = BIT_U64(cid & 63);
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS))
return __cmask_fetch_andnot(w, bit) & bit;
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if (!(old & bit))
return false;
new = old & ~bit;
if (__sync_val_compare_and_swap(w, old, new) == old)
return true;
}
scx_bpf_error("cmask_test_and_clear CAS exhausted at cid %u", cid);
return false;
}
static __always_inline void __cmask_set(u32 cid, struct scx_cmask __arena *m)
{
if (!__cmask_contains(cid, m))
return;
*__cmask_word(cid, m) |= BIT_U64(cid & 63);
}
static __always_inline void __cmask_clear(u32 cid, struct scx_cmask __arena *m)
{
if (!__cmask_contains(cid, m))
return;
*__cmask_word(cid, m) &= ~BIT_U64(cid & 63);
}
static __always_inline bool __cmask_test_and_set(u32 cid, struct scx_cmask __arena *m)
{
u64 bit = BIT_U64(cid & 63);
u64 __arena *w;
u64 prev;
if (!__cmask_contains(cid, m))
return false;
w = __cmask_word(cid, m);
prev = *w & bit;
*w |= bit;
return prev;
}
static __always_inline bool __cmask_test_and_clear(u32 cid, struct scx_cmask __arena *m)
{
u64 bit = BIT_U64(cid & 63);
u64 __arena *w;
u64 prev;
if (!__cmask_contains(cid, m))
return false;
w = __cmask_word(cid, m);
prev = *w & bit;
*w &= ~bit;
return prev;
}
/* atomically or @mask into *@w */
static __always_inline void __cmask_word_or(u64 __arena *w, u64 mask)
{
u64 old, new;
u32 i;
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
__cmask_fetch_or(w, mask);
return;
}
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if ((old & mask) == mask)
return;
new = old | mask;
if (__sync_val_compare_and_swap(w, old, new) == old)
return;
}
scx_bpf_error("__cmask_word_or CAS exhausted");
}
/* atomically clear @mask bits in *@w */
static __always_inline void __cmask_word_andnot(u64 __arena *w, u64 mask)
{
u64 old, new;
u32 i;
if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
__cmask_fetch_andnot(w, mask);
return;
}
bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
old = *w;
if (!(old & mask))
return;
new = old & ~mask;
if (__sync_val_compare_and_swap(w, old, new) == old)
return;
}
scx_bpf_error("__cmask_word_andnot CAS exhausted");
}
/**
* cmask_full_range - Test whether every cid in [@start, @start + @nr) is set
* @m: cmask to test
* @start: first cid of the range
* @nr: number of cids in the range
*
* The range is clamped to @m's active range first. True when no clamped bit is
* clear, including when the clamped range is empty.
*/
static __always_inline bool cmask_full_range(const struct scx_cmask __arena *m, u32 start,
u32 nr)
{
u64 end = (u64)start + nr;
u32 wbase = m->base / 64;
u32 first_wi, last_wi, first_bit, last_bit, last, i;
if (start < m->base)
start = m->base;
if (end > m->base + m->nr_cids)
end = m->base + m->nr_cids;
if (start >= end)
return true;
last = end - 1;
first_wi = start / 64 - wbase;
last_wi = last / 64 - wbase;
first_bit = start & 63;
last_bit = last & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 wi = first_wi + i;
u64 mask = ~0ULL;
if (wi > last_wi)
break;
if (wi == first_wi)
mask &= GENMASK_U64(63, first_bit);
if (wi == last_wi)
mask &= GENMASK_U64(last_bit, 0);
if ((m->bits[wi] & mask) != mask)
return false;
}
return true;
}
/**
* cmask_set_range - Set every cid in [@start, @start + @nr)
* @m: cmask to modify
* @start: first cid of the range
* @nr: number of cids in the range
*
* The range is clamped to @m's active range first. Words are updated atomically
* so concurrent updates of other bits sharing a word are never lost. The range
* as a whole does not transition atomically.
*/
static __always_inline void cmask_set_range(struct scx_cmask __arena *m, u32 start, u32 nr)
{
u64 end = (u64)start + nr;
u32 wbase = m->base / 64;
u32 first_wi, last_wi, first_bit, last_bit, last, i;
if (start < m->base)
start = m->base;
if (end > m->base + m->nr_cids)
end = m->base + m->nr_cids;
if (start >= end)
return;
last = end - 1;
first_wi = start / 64 - wbase;
last_wi = last / 64 - wbase;
first_bit = start & 63;
last_bit = last & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 wi = first_wi + i;
u64 mask = ~0ULL;
if (wi > last_wi)
break;
if (wi == first_wi)
mask &= GENMASK_U64(63, first_bit);
if (wi == last_wi)
mask &= GENMASK_U64(last_bit, 0);
__cmask_word_or(&m->bits[wi], mask);
}
}
/**
* cmask_clear_range - Clear every cid in [@start, @start + @nr)
* @m: cmask to modify
* @start: first cid of the range
* @nr: number of cids in the range
*
* The range is clamped to @m's active range first. Words are updated atomically
* so concurrent updates of other bits sharing a word are never lost. The range
* as a whole does not transition atomically.
*/
static __always_inline void cmask_clear_range(struct scx_cmask __arena *m, u32 start, u32 nr)
{
u64 end = (u64)start + nr;
u32 wbase = m->base / 64;
u32 first_wi, last_wi, first_bit, last_bit, last, i;
if (start < m->base)
start = m->base;
if (end > m->base + m->nr_cids)
end = m->base + m->nr_cids;
if (start >= end)
return;
last = end - 1;
first_wi = start / 64 - wbase;
last_wi = last / 64 - wbase;
first_bit = start & 63;
last_bit = last & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 wi = first_wi + i;
u64 mask = ~0ULL;
if (wi > last_wi)
break;
if (wi == first_wi)
mask &= GENMASK_U64(63, first_bit);
if (wi == last_wi)
mask &= GENMASK_U64(last_bit, 0);
__cmask_word_andnot(&m->bits[wi], mask);
}
}
static __always_inline void cmask_zero(struct scx_cmask __arena *m)
{
u32 nr_words = CMASK_NR_WORDS(m->nr_cids), i;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= nr_words)
break;
m->bits[i] = 0;
}
}
/**
* cmask_fill - Set every cid in @m's active range
* @m: cmask to fill
*
* Counterpart to cmask_zero(). Storage past the active range is left as is,
* matching the kernel-side scx_cmask_fill().
*/
static __always_inline void cmask_fill(struct scx_cmask __arena *m)
{
u32 nr_words, head_bits, tail_bits, i;
if (!m->nr_cids)
return;
nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= nr_words)
break;
m->bits[i] = ~0LLU;
}
/* clear word-0 bits below base */
head_bits = m->base & 63;
if (head_bits)
m->bits[0] &= ~((1LLU << head_bits) - 1);
/* clear last-word bits at or past base + nr_cids */
tail_bits = (m->base + m->nr_cids) & 63;
if (tail_bits)
m->bits[nr_words - 1] &= (1LLU << tail_bits) - 1;
}
/**
* cmask_empty - Test whether no cid is set in @m's active range
* @m: cmask to test
*
* Scans the words the active range spans, whose bits outside the range every
* mutator keeps zero.
*/
static __always_inline bool cmask_empty(const struct scx_cmask __arena *m)
{
u32 nr_words = cmask_nr_words(m), i;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= nr_words)
break;
if (m->bits[i])
return false;
}
return true;
}
/*
* BPF_-prefixed to avoid colliding with the kernel's anonymous CMASK_OP_*
* enum in ext/cid.c, which is exported via BTF and reachable through
* vmlinux.h.
*/
enum {
BPF_CMASK_OP_AND,
BPF_CMASK_OP_OR,
BPF_CMASK_OP_COPY,
BPF_CMASK_OP_ANDNOT,
};
/* range bits of word @k of @m, which spans @nr words: 0 past the span */
static __always_inline u64 __cmask_word_range(const struct scx_cmask __arena *m, u32 k,
u32 nr)
{
u64 bits = ~0ULL;
if (k >= nr)
return 0;
if (k == 0)
bits &= GENMASK_U64(63, m->base & 63);
if (k == nr - 1)
bits &= GENMASK_U64((m->base + m->nr_cids - 1) & 63, 0);
return bits;
}
/*
* The binary operations combine cmasks of any active ranges. A cmask holds bits
* only for its own range and reads as the operation's identity outside it, all
* ones for AND and all zeros for OR, so an operand alters only the bits it
* covers: ANDing a shard's mask into a global one edits that shard's window and
* leaves the rest of the global mask alone. ANDNOT is AND with @src2 inverted
* over @src2's range, so where only @src2 has range the result is ~@src2.
*
* @dst is written on its range intersected with the union of the sources'
* ranges and untouched elsewhere, and its padding bits outside its own range
* stay zero, the invariant every cmask mutator keeps. @dst may alias either
* source, and the two sources may be one mask. Returns whether @dst has a bit
* set afterwards, over all of @dst's range.
*
* The operations are global functions, verified once per program. Inlined,
* every data-dependent branch in this loop would multiply the states the
* verifier explores per iteration at every call site.
*/
static __always_inline bool __cmask_op(struct scx_cmask __arena *dst,
const struct scx_cmask __arena *src1,
const struct scx_cmask __arena *src2, int op)
{
u32 d_first = dst->base / 64, d_nr = cmask_nr_words(dst);
u32 s1_first = src1->base / 64, s1_nr = cmask_nr_words(src1);
u32 s2_first = src2->base / 64, s2_nr = cmask_nr_words(src2);
u64 any = bpf_arena_loop_zero;
u32 i;
bpf_arena_for(i, 0, d_nr) {
u32 k1 = d_first + i - s1_first, k2 = d_first + i - s2_first;
u64 m1 = __cmask_word_range(src1, k1, s1_nr);
u64 m2 = op == BPF_CMASK_OP_COPY ? m1 : __cmask_word_range(src2, k2, s2_nr);
u64 um = __cmask_word_range(dst, i, d_nr) & (m1 | m2);
u64 dv = dst->bits[i], v1, v2, rv;
if (um) {
/* a word in range holds zeros outside the range */
v1 = m1 ? src1->bits[k1] : 0;
v2 = m2 ? src2->bits[k2] : 0;
if (op == BPF_CMASK_OP_AND)
rv = (v1 | ~m1) & (v2 | ~m2);
else if (op == BPF_CMASK_OP_OR)
rv = v1 | v2;
else if (op == BPF_CMASK_OP_ANDNOT)
rv = (v1 | ~m1) & ~v2;
else
rv = v1;
dv = (dv & ~um) | (rv & um);
dst->bits[i] = dv;
}
any |= dv;
}
return any != 0;
}
/**
* cmask_and - Store @src1 AND @src2 in @dst
* @dst: destination, may be @src1 or @src2
* @src1: first operand
* @src2: second operand
*
* See __cmask_op() for how the ranges combine. Return whether @dst has a bit
* set afterwards.
*/
__weak bool cmask_and(struct scx_cmask __arena __arg_arena *dst,
const struct scx_cmask __arena __arg_arena *src1,
const struct scx_cmask __arena __arg_arena *src2)
{
return __cmask_op(dst, src1, src2, BPF_CMASK_OP_AND);
}
/**
* cmask_or - Store @src1 OR @src2 in @dst
* @dst: destination, may be @src1 or @src2
* @src1: first operand
* @src2: second operand
*
* See __cmask_op() for how the ranges combine. Return whether @dst has a bit
* set afterwards.
*/
__weak bool cmask_or(struct scx_cmask __arena __arg_arena *dst,
const struct scx_cmask __arena __arg_arena *src1,
const struct scx_cmask __arena __arg_arena *src2)
{
return __cmask_op(dst, src1, src2, BPF_CMASK_OP_OR);
}
/**
* cmask_andnot - Store @src1 AND NOT @src2 in @dst
* @dst: destination, may be @src1 or @src2
* @src1: operand to remove bits from
* @src2: bits to remove, inverted over its own range
*
* See __cmask_op() for how the ranges combine. Return whether @dst has a bit
* set afterwards.
*/
__weak bool cmask_andnot(struct scx_cmask __arena __arg_arena *dst,
const struct scx_cmask __arena __arg_arena *src1,
const struct scx_cmask __arena __arg_arena *src2)
{
return __cmask_op(dst, src1, src2, BPF_CMASK_OP_ANDNOT);
}
/**
* cmask_copy - Copy @src into @dst
* @dst: destination
* @src: source
*
* The single-source case of __cmask_op(): @dst is written over @src's range and
* untouched elsewhere.
*/
__weak void cmask_copy(struct scx_cmask __arena __arg_arena *dst,
const struct scx_cmask __arena __arg_arena *src)
{
__cmask_op(dst, src, src, BPF_CMASK_OP_COPY);
}
/*
* True iff @a and @b have identical bits over their (assumed equal) range.
* Callers are expected to pass same-shape cmasks; differing shapes always
* compare unequal.
*/
static __always_inline bool cmask_equal(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b)
{
u32 nr_words, i;
if (a->base != b->base || a->nr_cids != b->nr_cids)
return false;
if (a->nr_cids == 0)
return true;
nr_words = (a->base + a->nr_cids - 1) / 64 - a->base / 64 + 1;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= nr_words)
break;
if (a->bits[i] != b->bits[i])
return false;
}
return true;
}
/**
* cmask_next_set - find the first set bit at or after @cid
* @m: cmask to search
* @cid: starting cid (clamped to @m->base if below)
*
* Returns the smallest set cid in [@cid, @m->base + @m->nr_cids), or
* @m->base + @m->nr_cids if none (the out-of-range sentinel matches the
* termination condition used by cmask_for_each()).
*/
static __always_inline u32 cmask_next_set(const struct scx_cmask __arena *m, u32 cid)
{
u32 end = m->base + m->nr_cids;
u32 base = m->base / 64;
u32 last_wi = (end - 1) / 64 - base;
u32 start_wi, start_bit, i;
if (cid < m->base)
cid = m->base;
if (cid >= end)
return end;
start_wi = cid / 64 - base;
start_bit = cid & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 wi = start_wi + i;
u64 word;
u32 found;
if (wi > last_wi)
break;
word = m->bits[wi];
if (i == 0)
word &= GENMASK_U64(63, start_bit);
if (!word)
continue;
found = (base + wi) * 64 + ctzll(word);
if (found >= end)
return end;
return found;
}
return end;
}
static __always_inline u32 cmask_first_set(const struct scx_cmask __arena *m)
{
return cmask_next_set(m, m->base);
}
#define cmask_for_each(cid, m) \
for ((cid) = cmask_first_set(m); \
(cid) < (m)->base + (m)->nr_cids && can_loop; \
(cid) = cmask_next_set((m), (cid) + 1))
/*
* True iff every bit set in @a is also set in @b. Matches the kernel-side
* scx_cmask_subset(): ranges don't need to nest, and set bits of @a outside
* @b's range fail the test.
*/
static __always_inline bool cmask_subset(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b)
{
u32 a_end = a->base + a->nr_cids;
u32 b_end = b->base + b->nr_cids;
u32 a_wbase = a->base / 64;
u32 b_wbase = b->base / 64;
u32 lo = a->base > b->base ? a->base : b->base;
u32 hi = a_end < b_end ? a_end : b_end;
u32 lo_word, hi_word, i;
/* set bits of @a outside @b's range can't be in @b */
if (a->base < b->base &&
cmask_next_set(a, a->base) < (b->base < a_end ? b->base : a_end))
return false;
if (a_end > b_end &&
cmask_next_set(a, a->base > b_end ? a->base : b_end) < a_end)
return false;
if (lo >= hi)
return true;
/*
* Walk the words the range intersection spans. Plain word tests
* suffice: the scans above guarantee @a has no set bit outside @b's
* range and padding bits are kept clear by all cmask helpers.
*/
lo_word = lo / 64;
hi_word = (hi - 1) / 64;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 w = lo_word + i;
if (w > hi_word)
break;
if (a->bits[w - a_wbase] & ~b->bits[w - b_wbase])
return false;
}
return true;
}
/*
* Population count over [base, base + nr_cids). Padding bits in the head/tail
* words are guaranteed zero by the mutating helpers, so a flat popcount over
* the words the range spans is correct.
*/
static __always_inline u32 cmask_weight(const struct scx_cmask __arena *m)
{
u32 nr_words, i;
/* callers compare the sum, see bpf_arena_loop_zero */
u32 count = bpf_arena_loop_zero;
if (!m->nr_cids)
return 0;
nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
if (i >= nr_words)
break;
count += __builtin_popcountll(m->bits[i]);
}
return count;
}
/* true if @a and @b share any set bit, over the intersection of their ranges */
static __always_inline bool cmask_intersects(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b)
{
u32 a_end = a->base + a->nr_cids;
u32 b_end = b->base + b->nr_cids;
u32 lo = a->base > b->base ? a->base : b->base;
u32 hi = a_end < b_end ? a_end : b_end;
u32 a_base = a->base / 64;
u32 b_base = b->base / 64;
u32 lo_word, hi_word, i;
u64 head_mask, tail_mask;
if (lo >= hi)
return false;
lo_word = lo / 64;
hi_word = (hi - 1) / 64;
head_mask = GENMASK_U64(63, lo & 63);
tail_mask = GENMASK_U64((hi - 1) & 63, 0);
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 w = lo_word + i;
u64 mask, av, bv;
if (w > hi_word)
break;
mask = GENMASK_U64(63, 0);
if (w == lo_word)
mask &= head_mask;
if (w == hi_word)
mask &= tail_mask;
av = a->bits[w - a_base] & mask;
bv = b->bits[w - b_base] & mask;
if (av & bv)
return true;
}
return false;
}
/*
* Find the next cid set in both @a and @b at or after @start, bounded by the
* intersection of the two ranges. Return a->base + a->nr_cids if none found.
*
* Building block for cmask_next_and_set_wrap(). Callers that want a bounded
* scan without wrap call this directly.
*/
static __always_inline u32 cmask_next_and_set(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b,
u32 start)
{
u32 a_end = a->base + a->nr_cids;
u32 b_end = b->base + b->nr_cids;
u32 a_wbase = a->base / 64;
u32 b_wbase = b->base / 64;
u32 lo = a->base > b->base ? a->base : b->base;
u32 hi = a_end < b_end ? a_end : b_end;
u32 last_wi, start_wi, start_bit, i;
if (lo >= hi)
return a_end;
if (start < lo)
start = lo;
if (start >= hi)
return a_end;
last_wi = (hi - 1) / 64;
start_wi = start / 64;
start_bit = start & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 abs_wi = start_wi + i;
u64 word;
u32 found;
if (abs_wi > last_wi)
break;
word = a->bits[abs_wi - a_wbase] & b->bits[abs_wi - b_wbase];
if (i == 0)
word &= GENMASK_U64(63, start_bit);
if (!word)
continue;
found = abs_wi * 64 + ctzll(word);
if (found >= hi)
return a_end;
return found;
}
return a_end;
}
/*
* Find the next set cid in @m at or after @start, wrapping to @m->base if no
* set bit is found in [start, m->base + m->nr_cids). Return m->base +
* m->nr_cids if @m is empty.
*
* Callers do round-robin distribution by passing (last_cid + 1) as @start.
*/
static __always_inline u32 cmask_next_set_wrap(const struct scx_cmask __arena *m,
u32 start)
{
u32 end = m->base + m->nr_cids;
u32 found;
found = cmask_next_set(m, start);
if (found < end || start <= m->base)
return found;
found = cmask_next_set(m, m->base);
return found < start ? found : end;
}
/**
* cmask_end - One past the last cid of @m's active range
* @m: cmask of interest
*
* The scan helpers return this value when no set cid is found.
*/
static __always_inline u32 cmask_end(const struct scx_cmask __arena *m)
{
return m->base + m->nr_cids;
}
/*
* Find the next cid set in both @a and @b at or after @start, wrapping to
* @a->base if none found in the forward half. Return a->base + a->nr_cids
* if the intersection is empty.
*
* Callers do round-robin distribution by passing (last_cid + 1) as @start.
*/
static __always_inline u32 cmask_next_and_set_wrap(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b,
u32 start)
{
u32 a_end = a->base + a->nr_cids;
u32 found;
found = cmask_next_and_set(a, b, start);
if (found < a_end || start <= a->base)
return found;
found = cmask_next_and_set(a, b, a->base);
return found < start ? found : a_end;
}
/*
* The distribute helpers rotate through one slot per cid, a cacheline each so
* picks on one CPU do not bounce the line of another. A missing allocation is a
* setup bug and aborts the scheduler. On a CPU outside the scheduler's cid
* space the helpers fall back to the first matching cid.
*
* TODO: The slots are one allocation without node placement. Move them to a
* per-cid arena allocator once the library has one, for node-local slots
* without the cacheline padding.
*/
struct cmask_rotor_slot {
u32 cid;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
struct cmask_rotor_slot __arena *cmask_distribute_rotors __weak;
/**
* cmask_distribute_init - Allocate the distribute rotor from @map
* @map: the scheduler's arena map
*
* The shared arena init calls this once. A scheduler with its own arena calls
* it before the first distribute pick. On a kernel without the cid kfuncs
* nothing can pick, so the call allocates nothing and succeeds. Return 0 on
* success, -ENOMEM if the allocation fails.
*/
static __always_inline int cmask_distribute_init(void *map)
{
u64 size;
u32 pages;
/* no cid kfuncs means no picks and nothing to allocate */
if (!bpf_ksym_exists(scx_bpf_nr_cids))
return 0;
size = scx_bpf_nr_cids() * sizeof(*cmask_distribute_rotors);
pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
cmask_distribute_rotors = bpf_arena_alloc_pages(map, NULL, pages, NUMA_NO_NODE, 0);
return cmask_distribute_rotors ? 0 : -ENOMEM;
}
static __always_inline u32 __arena *__cmask_distribute_rotor(void)
{
s32 cid = scx_bpf_this_cid();
if (unlikely(!cmask_distribute_rotors)) {
scx_bpf_error("cmask distribute rotor not allocated");
return NULL;
}
if (cid < 0)
return NULL;
return &cmask_distribute_rotors[cid].cid;
}
/**
* cmask_any_distribute - Pick a set cid, spreading successive picks
* @m: cmask to pick from
*
* Counterpart of bpf_cpumask_any_distribute(): a per-cid rotor makes successive
* picks rotate through the set cids instead of repeating the first one. Returns
* cmask_end(@m) if @m is empty.
*/
static __always_inline u32 cmask_any_distribute(const struct scx_cmask __arena *m)
{
u32 __arena *rotor = __cmask_distribute_rotor();
u32 pick;
if (unlikely(!rotor))
return cmask_first_set(m);
pick = cmask_next_set_wrap(m, *rotor + 1);
if (pick < cmask_end(m))
*rotor = pick;
return pick;
}
/**
* cmask_any_and_distribute - Pick a cid set in both masks, spreading picks
* @a: first cmask, the scan is bounded by its range
* @b: second cmask
*
* Counterpart of bpf_cpumask_any_and_distribute(). Shares the rotor with
* cmask_any_distribute() the same way the kernel counterparts share theirs.
* Returns cmask_end(@a) if the intersection is empty.
*/
static __always_inline u32 cmask_any_and_distribute(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b)
{
u32 __arena *rotor = __cmask_distribute_rotor();
u32 pick;
if (unlikely(!rotor))
return cmask_next_and_set(a, b, a->base);
pick = cmask_next_and_set_wrap(a, b, *rotor + 1);
if (pick < cmask_end(a))
*rotor = pick;
return pick;
}
/**
* cmask_distribute_rotor_pos - Last cid picked by the distribute helpers
*
* For callers that anchor scans of their own on the shared rotor. Returns 0
* when nothing has been picked on this cid yet or the CPU is outside the cid
* space.
*/
static __always_inline u32 cmask_distribute_rotor_pos(void)
{
u32 __arena *rotor = __cmask_distribute_rotor();
if (unlikely(!rotor))
return 0;
return *rotor;
}
/*
* Like cmask_next_and_set() but over the intersection of THREE masks. Return
* a->base + a->nr_cids if no cid is set in all three at or after @start.
*/
static __always_inline u32 cmask_next_and2_set(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b,
const struct scx_cmask __arena *c,
u32 start)
{
u32 a_end = a->base + a->nr_cids;
u32 b_end = b->base + b->nr_cids;
u32 c_end = c->base + c->nr_cids;
u32 a_wbase = a->base / 64;
u32 b_wbase = b->base / 64;
u32 c_wbase = c->base / 64;
u32 lo = a->base > b->base ? a->base : b->base;
u32 hi = a_end < b_end ? a_end : b_end;
u32 last_wi, start_wi, start_bit, i;
lo = lo > c->base ? lo : c->base;
hi = hi < c_end ? hi : c_end;
if (lo >= hi)
return a_end;
if (start < lo)
start = lo;
if (start >= hi)
return a_end;
last_wi = (hi - 1) / 64;
start_wi = start / 64;
start_bit = start & 63;
bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
u32 abs_wi = start_wi + i;
u64 word;
u32 found;
if (abs_wi > last_wi)
break;
word = a->bits[abs_wi - a_wbase] & b->bits[abs_wi - b_wbase] &
c->bits[abs_wi - c_wbase];
if (i == 0)
word &= GENMASK_U64(63, start_bit);
if (!word)
continue;
found = abs_wi * 64 + ctzll(word);
if (found >= hi)
return a_end;
return found;
}
return a_end;
}
/*
* Round-robin variant of cmask_next_and2_set(): wrap to @a->base if the
* three-way intersection has no cid in the forward half. Return a->base +
* a->nr_cids if empty.
*/
static __always_inline u32 cmask_next_and2_set_wrap(const struct scx_cmask __arena *a,
const struct scx_cmask __arena *b,
const struct scx_cmask __arena *c,
u32 start)
{
u32 a_end = a->base + a->nr_cids;
u32 found;
found = cmask_next_and2_set(a, b, c, start);
if (found < a_end || start <= a->base)
return found;
found = cmask_next_and2_set(a, b, c, a->base);
return found < start ? found : a_end;
}
/**
* cmask_from_cpumask - translate a kernel cpumask to a cid-space cmask
* @m: cmask to fill. Zeroed first; only bits within [@m->base, @m->base +
* @m->nr_cids) are updated - cpus mapping to cids outside that range
* are ignored.
* @cpumask: kernel cpumask to translate
*
* For each cpu in @cpumask, set the cpu's cid in @m. Caller must ensure
* @cpumask stays stable across the call (e.g. RCU read lock for
* task->cpus_ptr).
*/
static __always_inline void cmask_from_cpumask(struct scx_cmask __arena *m,
const struct cpumask *cpumask)
{
u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
s32 cpu;
cmask_zero(m);
bpf_arena_for(cpu, 0, nr_cpu_ids) {
s32 cid;
if (!bpf_cpumask_test_cpu(cpu, cpumask))
continue;
cid = scx_bpf_cpu_to_cid(cpu);
if (cid >= 0)
__cmask_set(cid, m);
}
}
#endif /* __SCX_CID_BPF_H */