xref: /linux/tools/sched_ext/include/scx/cid.bpf.h (revision e0bb535017713171b7c5c595bd08c2e8527de863)
1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3  * BPF-side helpers for cids and cmasks. See kernel/sched/ext/cid.h for the
4  * authoritative layout and semantics. The BPF-side helpers use the cmask_*
5  * naming (no scx_ prefix); cmask is the SCX bitmap type so the prefix is
6  * redundant in BPF code. Every helper is inline except the binary operations,
7  * which are weak global functions defined here, so there is no .c counterpart.
8  *
9  * Included by scx/common.bpf.h; don't include directly.
10  *
11  * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
12  * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
13  */
14 #ifndef __SCX_CID_BPF_H
15 #define __SCX_CID_BPF_H
16 
17 #include "bpf_arena_common.bpf.h"
18 
19 /* libbpf's bpf_helpers.h defines it from 1.4 on */
20 #ifndef __arg_arena
21 #define __arg_arena __attribute((btf_decl_tag("arg:arena")))
22 #endif
23 
24 #ifndef BIT_U64
25 #define BIT_U64(nr)		(1ULL << (nr))
26 #endif
27 #ifndef GENMASK_U64
28 #define GENMASK_U64(h, l)	((~0ULL << (l)) & (~0ULL >> (63 - (h))))
29 #endif
30 
31 /*
32  * Storage cap for bounded loops over bits[]. Sized to cover NR_CPUS=8192 with
33  * one extra word for head-misalignment. Increase if deployment targets larger
34  * NR_CPUS.
35  */
36 #ifndef CMASK_MAX_WORDS
37 #define CMASK_MAX_WORDS 129
38 #endif
39 
40 /*
41  * Mirrors SCX_CMASK_NR_WORDS in kernel/sched/ext/types.h. The u64 cast keeps
42  * the +63 from wrapping when @nr_cids is near U32_MAX, so cmask_reframe()
43  * bounds-checking the result against alloc_words catches the overflow instead
44  * of seeing a small value.
45  */
46 #define CMASK_NR_WORDS(nr_cids)		((u32)(((u64)(nr_cids) + 63) / 64 + 1))
47 
48 static __always_inline bool __cmask_contains(u32 cid, const struct scx_cmask __arena *m)
49 {
50 	return cid >= m->base && cid < m->base + m->nr_cids;
51 }
52 
53 static __always_inline u64 __arena *__cmask_word(u32 cid, const struct scx_cmask __arena *m)
54 {
55 	return (u64 __arena *)&m->bits[cid / 64 - m->base / 64];
56 }
57 
58 /**
59  * __cmask_init - Initialize @m with explicit storage capacity
60  * @m: cmask to initialize
61  * @base: first cid of the active range
62  * @nr_cids: number of cids in the active range
63  * @alloc_cids: storage capacity in cids, at least @nr_cids
64  *
65  * Use when storage is sized larger than the initial active range. All of
66  * bits[] is zeroed.
67  */
68 static __always_inline void __cmask_init(struct scx_cmask __arena *m, u32 base,
69 					 u32 nr_cids, u32 alloc_cids)
70 {
71 	u32 alloc_words, i;
72 
73 	if (unlikely(nr_cids > alloc_cids)) {
74 		scx_bpf_error("__cmask_init: nr_cids=%u exceeds alloc_cids=%u",
75 			      nr_cids, alloc_cids);
76 		return;
77 	}
78 	alloc_words = CMASK_NR_WORDS(alloc_cids);
79 
80 	m->base = base;
81 	m->nr_cids = nr_cids;
82 	m->alloc_words = alloc_words;
83 
84 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
85 		if (i >= alloc_words)
86 			break;
87 		m->bits[i] = 0;
88 	}
89 }
90 
91 /**
92  * cmask_init - Initialize @m on tight storage
93  * @m: cmask to initialize
94  * @base: first cid of the active range
95  * @nr_cids: number of cids in the active range
96  *
97  * All of bits[] is zeroed.
98  */
99 static __always_inline void cmask_init(struct scx_cmask __arena *m, u32 base, u32 nr_cids)
100 {
101 	__cmask_init(m, base, nr_cids, nr_cids);
102 }
103 
104 /**
105  * cmask_reframe - Reshape @m's active range without resizing storage
106  * @m: cmask to reframe
107  * @base: new active range base
108  * @nr_cids: new active range length, must fit within @m->alloc_words
109  *
110  * Body bits within the new range become garbage - only the head and tail
111  * words are zeroed to keep the padding invariant.
112  */
113 static __always_inline void cmask_reframe(struct scx_cmask __arena *m, u32 base, u32 nr_cids)
114 {
115 	if (CMASK_NR_WORDS(nr_cids) > m->alloc_words) {
116 		scx_bpf_error("cmask_reframe: nr_cids=%u exceeds alloc_words=%u",
117 			      nr_cids, m->alloc_words);
118 		return;
119 	}
120 	if (nr_cids) {
121 		u32 last_word = ((base & 63) + nr_cids - 1) / 64;
122 
123 		m->bits[0] = 0;
124 		m->bits[last_word] = 0;
125 	}
126 	m->base = base;
127 	m->nr_cids = nr_cids;
128 }
129 
130 /**
131  * cmask_nr_words - Words of bits[] that @m's active range spans
132  * @m: cmask to measure
133  *
134  * @m->base need not be word aligned: bits[0] covers the whole word @m->base
135  * falls in, so the count is taken from that word, not from @m->base.
136  */
137 static __always_inline u32 cmask_nr_words(const struct scx_cmask __arena *m)
138 {
139 	u32 wbase = m->base / 64;
140 
141 	return m->nr_cids ? (m->base + m->nr_cids - 1) / 64 - wbase + 1 : 0;
142 }
143 
144 /**
145  * cmask_word - Read word @k of @m
146  * @m: cmask to read
147  * @k: word index, counted from the word @m->base falls in
148  *
149  * A word past the active range reads as 0, so a scan that looks one word
150  * ahead (at an SMT sibling that falls in the next word, say) needs no bound
151  * of its own.
152  */
153 static __always_inline u64 cmask_word(const struct scx_cmask __arena *m, u32 k)
154 {
155 	if (k >= cmask_nr_words(m))
156 		return 0;
157 	return m->bits[k];
158 }
159 
160 /**
161  * cmask_range_word - Bits of word @k of @m that fall in [@start, @start + @nr)
162  * @m: cmask the word belongs to
163  * @k: word index, counted from the word @m->base falls in
164  * @start: first cid of the range
165  * @nr: number of cids in the range
166  *
167  * Scoping a scan to a domain that is contiguous in cid space, an LLC or a
168  * node, is then one AND per word, rather than a test of the ends of the
169  * range at every bit or a mask kept per domain.
170  */
171 static __always_inline u64 cmask_range_word(const struct scx_cmask __arena *m,
172 					    u32 k, u32 start, u32 nr)
173 {
174 	u64 wlo = (u64)(m->base / 64 + k) * 64, whi = wlo + 64;
175 	u64 lo = start, hi = (u64)start + nr;
176 
177 	if (lo < wlo)
178 		lo = wlo;
179 	if (hi > whi)
180 		hi = whi;
181 	if (lo >= hi)
182 		return 0;
183 
184 	return GENMASK_U64(hi - wlo - 1, lo - wlo);
185 }
186 
187 /**
188  * __cmask_test - Test a cid without checking it against the active range
189  * @cid: cid to test
190  * @m: cmask to test
191  *
192  * The caller must already know that @cid lies within
193  * [@m->base, @m->base + @m->nr_cids), or the read runs off @m->bits.
194  *
195  * For hot scans that have already bounded @cid, where the per-call range check
196  * is repeated work on every candidate.
197  */
198 static __always_inline bool __cmask_test(u32 cid, const struct scx_cmask __arena *m)
199 {
200 	return *__cmask_word(cid, m) & BIT_U64(cid & 63);
201 }
202 
203 static __always_inline bool cmask_test(u32 cid, const struct scx_cmask __arena *m)
204 {
205 	if (!__cmask_contains(cid, m))
206 		return false;
207 	return __cmask_test(cid, m);
208 }
209 
210 /*
211  * The bit helpers use the fetching bitwise builtins, which order like the
212  * cmpxchg they replace, and not every JIT accepts those on arena pointers. The
213  * loader sets SCX_LIB_FEAT_ARENA_FETCH_BITOPS when a probe with the fetching
214  * form loads, and the helpers run a cmpxchg loop otherwise. The fetched value
215  * is consumed through barrier_var() because clang 19 lowers a builtin whose
216  * result is unused to the non-fetching form, which is relaxed on arm64.
217  *
218  * CMASK_CAS_TRIES is sized so exhausting it means seconds of real spinning
219  * on one word, past any plausible contention. Abort hard.
220  */
221 #define CMASK_CAS_TRIES		(1U << 23)
222 
223 static __always_inline u64 __cmask_fetch_or(u64 __arena *w, u64 mask)
224 {
225 	u64 old = __atomic_fetch_or(w, mask, __ATOMIC_ACQ_REL);
226 
227 	barrier_var(old);
228 	return old;
229 }
230 
231 static __always_inline u64 __cmask_fetch_andnot(u64 __arena *w, u64 mask)
232 {
233 	u64 old = __atomic_fetch_and(w, ~mask, __ATOMIC_ACQ_REL);
234 
235 	barrier_var(old);
236 	return old;
237 }
238 
239 static __always_inline void cmask_set(u32 cid, struct scx_cmask __arena *m)
240 {
241 	u64 __arena *w;
242 	u64 bit, old, new;
243 	u32 i;
244 
245 	if (!__cmask_contains(cid, m))
246 		return;
247 	w = __cmask_word(cid, m);
248 	bit = BIT_U64(cid & 63);
249 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
250 		__cmask_fetch_or(w, bit);
251 		return;
252 	}
253 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
254 		old = *w;
255 		if (old & bit)
256 			return;
257 		new = old | bit;
258 		if (__sync_val_compare_and_swap(w, old, new) == old)
259 			return;
260 	}
261 	scx_bpf_error("cmask_set CAS exhausted at cid %u", cid);
262 }
263 
264 static __always_inline void cmask_clear(u32 cid, struct scx_cmask __arena *m)
265 {
266 	u64 __arena *w;
267 	u64 bit, old, new;
268 	u32 i;
269 
270 	if (!__cmask_contains(cid, m))
271 		return;
272 	w = __cmask_word(cid, m);
273 	bit = BIT_U64(cid & 63);
274 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
275 		__cmask_fetch_andnot(w, bit);
276 		return;
277 	}
278 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
279 		old = *w;
280 		if (!(old & bit))
281 			return;
282 		new = old & ~bit;
283 		if (__sync_val_compare_and_swap(w, old, new) == old)
284 			return;
285 	}
286 	scx_bpf_error("cmask_clear CAS exhausted at cid %u", cid);
287 }
288 
289 static __always_inline bool cmask_test_and_set(u32 cid, struct scx_cmask __arena *m)
290 {
291 	u64 __arena *w;
292 	u64 bit, old, new;
293 	u32 i;
294 
295 	if (!__cmask_contains(cid, m))
296 		return false;
297 	w = __cmask_word(cid, m);
298 	bit = BIT_U64(cid & 63);
299 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS))
300 		return __cmask_fetch_or(w, bit) & bit;
301 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
302 		old = *w;
303 		if (old & bit)
304 			return true;
305 		new = old | bit;
306 		if (__sync_val_compare_and_swap(w, old, new) == old)
307 			return false;
308 	}
309 	scx_bpf_error("cmask_test_and_set CAS exhausted at cid %u", cid);
310 	return false;
311 }
312 
313 static __always_inline bool cmask_test_and_clear(u32 cid, struct scx_cmask __arena *m)
314 {
315 	u64 __arena *w;
316 	u64 bit, old, new;
317 	u32 i;
318 
319 	if (!__cmask_contains(cid, m))
320 		return false;
321 	w = __cmask_word(cid, m);
322 	bit = BIT_U64(cid & 63);
323 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS))
324 		return __cmask_fetch_andnot(w, bit) & bit;
325 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
326 		old = *w;
327 		if (!(old & bit))
328 			return false;
329 		new = old & ~bit;
330 		if (__sync_val_compare_and_swap(w, old, new) == old)
331 			return true;
332 	}
333 	scx_bpf_error("cmask_test_and_clear CAS exhausted at cid %u", cid);
334 	return false;
335 }
336 
337 static __always_inline void __cmask_set(u32 cid, struct scx_cmask __arena *m)
338 {
339 	if (!__cmask_contains(cid, m))
340 		return;
341 	*__cmask_word(cid, m) |= BIT_U64(cid & 63);
342 }
343 
344 static __always_inline void __cmask_clear(u32 cid, struct scx_cmask __arena *m)
345 {
346 	if (!__cmask_contains(cid, m))
347 		return;
348 	*__cmask_word(cid, m) &= ~BIT_U64(cid & 63);
349 }
350 
351 static __always_inline bool __cmask_test_and_set(u32 cid, struct scx_cmask __arena *m)
352 {
353 	u64 bit = BIT_U64(cid & 63);
354 	u64 __arena *w;
355 	u64 prev;
356 
357 	if (!__cmask_contains(cid, m))
358 		return false;
359 	w = __cmask_word(cid, m);
360 	prev = *w & bit;
361 	*w |= bit;
362 	return prev;
363 }
364 
365 static __always_inline bool __cmask_test_and_clear(u32 cid, struct scx_cmask __arena *m)
366 {
367 	u64 bit = BIT_U64(cid & 63);
368 	u64 __arena *w;
369 	u64 prev;
370 
371 	if (!__cmask_contains(cid, m))
372 		return false;
373 	w = __cmask_word(cid, m);
374 	prev = *w & bit;
375 	*w &= ~bit;
376 	return prev;
377 }
378 
379 /* atomically or @mask into *@w */
380 static __always_inline void __cmask_word_or(u64 __arena *w, u64 mask)
381 {
382 	u64 old, new;
383 	u32 i;
384 
385 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
386 		__cmask_fetch_or(w, mask);
387 		return;
388 	}
389 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
390 		old = *w;
391 		if ((old & mask) == mask)
392 			return;
393 		new = old | mask;
394 		if (__sync_val_compare_and_swap(w, old, new) == old)
395 			return;
396 	}
397 	scx_bpf_error("__cmask_word_or CAS exhausted");
398 }
399 
400 /* atomically clear @mask bits in *@w */
401 static __always_inline void __cmask_word_andnot(u64 __arena *w, u64 mask)
402 {
403 	u64 old, new;
404 	u32 i;
405 
406 	if (scx_lib_has(SCX_LIB_FEAT_ARENA_FETCH_BITOPS)) {
407 		__cmask_fetch_andnot(w, mask);
408 		return;
409 	}
410 	bpf_arena_for(i, 0, CMASK_CAS_TRIES) {
411 		old = *w;
412 		if (!(old & mask))
413 			return;
414 		new = old & ~mask;
415 		if (__sync_val_compare_and_swap(w, old, new) == old)
416 			return;
417 	}
418 	scx_bpf_error("__cmask_word_andnot CAS exhausted");
419 }
420 
421 /**
422  * cmask_full_range - Test whether every cid in [@start, @start + @nr) is set
423  * @m: cmask to test
424  * @start: first cid of the range
425  * @nr: number of cids in the range
426  *
427  * The range is clamped to @m's active range first. True when no clamped bit is
428  * clear, including when the clamped range is empty.
429  */
430 static __always_inline bool cmask_full_range(const struct scx_cmask __arena *m, u32 start,
431 					     u32 nr)
432 {
433 	u64 end = (u64)start + nr;
434 	u32 wbase = m->base / 64;
435 	u32 first_wi, last_wi, first_bit, last_bit, last, i;
436 
437 	if (start < m->base)
438 		start = m->base;
439 	if (end > m->base + m->nr_cids)
440 		end = m->base + m->nr_cids;
441 	if (start >= end)
442 		return true;
443 	last = end - 1;
444 
445 	first_wi = start / 64 - wbase;
446 	last_wi = last / 64 - wbase;
447 	first_bit = start & 63;
448 	last_bit = last & 63;
449 
450 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
451 		u32 wi = first_wi + i;
452 		u64 mask = ~0ULL;
453 
454 		if (wi > last_wi)
455 			break;
456 		if (wi == first_wi)
457 			mask &= GENMASK_U64(63, first_bit);
458 		if (wi == last_wi)
459 			mask &= GENMASK_U64(last_bit, 0);
460 		if ((m->bits[wi] & mask) != mask)
461 			return false;
462 	}
463 	return true;
464 }
465 
466 /**
467  * cmask_set_range - Set every cid in [@start, @start + @nr)
468  * @m: cmask to modify
469  * @start: first cid of the range
470  * @nr: number of cids in the range
471  *
472  * The range is clamped to @m's active range first. Words are updated atomically
473  * so concurrent updates of other bits sharing a word are never lost. The range
474  * as a whole does not transition atomically.
475  */
476 static __always_inline void cmask_set_range(struct scx_cmask __arena *m, u32 start, u32 nr)
477 {
478 	u64 end = (u64)start + nr;
479 	u32 wbase = m->base / 64;
480 	u32 first_wi, last_wi, first_bit, last_bit, last, i;
481 
482 	if (start < m->base)
483 		start = m->base;
484 	if (end > m->base + m->nr_cids)
485 		end = m->base + m->nr_cids;
486 	if (start >= end)
487 		return;
488 	last = end - 1;
489 
490 	first_wi = start / 64 - wbase;
491 	last_wi = last / 64 - wbase;
492 	first_bit = start & 63;
493 	last_bit = last & 63;
494 
495 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
496 		u32 wi = first_wi + i;
497 		u64 mask = ~0ULL;
498 
499 		if (wi > last_wi)
500 			break;
501 		if (wi == first_wi)
502 			mask &= GENMASK_U64(63, first_bit);
503 		if (wi == last_wi)
504 			mask &= GENMASK_U64(last_bit, 0);
505 		__cmask_word_or(&m->bits[wi], mask);
506 	}
507 }
508 
509 /**
510  * cmask_clear_range - Clear every cid in [@start, @start + @nr)
511  * @m: cmask to modify
512  * @start: first cid of the range
513  * @nr: number of cids in the range
514  *
515  * The range is clamped to @m's active range first. Words are updated atomically
516  * so concurrent updates of other bits sharing a word are never lost. The range
517  * as a whole does not transition atomically.
518  */
519 static __always_inline void cmask_clear_range(struct scx_cmask __arena *m, u32 start, u32 nr)
520 {
521 	u64 end = (u64)start + nr;
522 	u32 wbase = m->base / 64;
523 	u32 first_wi, last_wi, first_bit, last_bit, last, i;
524 
525 	if (start < m->base)
526 		start = m->base;
527 	if (end > m->base + m->nr_cids)
528 		end = m->base + m->nr_cids;
529 	if (start >= end)
530 		return;
531 	last = end - 1;
532 
533 	first_wi = start / 64 - wbase;
534 	last_wi = last / 64 - wbase;
535 	first_bit = start & 63;
536 	last_bit = last & 63;
537 
538 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
539 		u32 wi = first_wi + i;
540 		u64 mask = ~0ULL;
541 
542 		if (wi > last_wi)
543 			break;
544 		if (wi == first_wi)
545 			mask &= GENMASK_U64(63, first_bit);
546 		if (wi == last_wi)
547 			mask &= GENMASK_U64(last_bit, 0);
548 		__cmask_word_andnot(&m->bits[wi], mask);
549 	}
550 }
551 
552 static __always_inline void cmask_zero(struct scx_cmask __arena *m)
553 {
554 	u32 nr_words = CMASK_NR_WORDS(m->nr_cids), i;
555 
556 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
557 		if (i >= nr_words)
558 			break;
559 		m->bits[i] = 0;
560 	}
561 }
562 
563 /**
564  * cmask_fill - Set every cid in @m's active range
565  * @m: cmask to fill
566  *
567  * Counterpart to cmask_zero(). Storage past the active range is left as is,
568  * matching the kernel-side scx_cmask_fill().
569  */
570 static __always_inline void cmask_fill(struct scx_cmask __arena *m)
571 {
572 	u32 nr_words, head_bits, tail_bits, i;
573 
574 	if (!m->nr_cids)
575 		return;
576 	nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
577 
578 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
579 		if (i >= nr_words)
580 			break;
581 		m->bits[i] = ~0LLU;
582 	}
583 
584 	/* clear word-0 bits below base */
585 	head_bits = m->base & 63;
586 	if (head_bits)
587 		m->bits[0] &= ~((1LLU << head_bits) - 1);
588 
589 	/* clear last-word bits at or past base + nr_cids */
590 	tail_bits = (m->base + m->nr_cids) & 63;
591 	if (tail_bits)
592 		m->bits[nr_words - 1] &= (1LLU << tail_bits) - 1;
593 }
594 
595 /**
596  * cmask_empty - Test whether no cid is set in @m's active range
597  * @m: cmask to test
598  *
599  * Scans the words the active range spans, whose bits outside the range every
600  * mutator keeps zero.
601  */
602 static __always_inline bool cmask_empty(const struct scx_cmask __arena *m)
603 {
604 	u32 nr_words = cmask_nr_words(m), i;
605 
606 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
607 		if (i >= nr_words)
608 			break;
609 		if (m->bits[i])
610 			return false;
611 	}
612 	return true;
613 }
614 
615 /*
616  * BPF_-prefixed to avoid colliding with the kernel's anonymous CMASK_OP_*
617  * enum in ext/cid.c, which is exported via BTF and reachable through
618  * vmlinux.h.
619  */
620 enum {
621 	BPF_CMASK_OP_AND,
622 	BPF_CMASK_OP_OR,
623 	BPF_CMASK_OP_COPY,
624 	BPF_CMASK_OP_ANDNOT,
625 };
626 
627 /* range bits of word @k of @m, which spans @nr words: 0 past the span */
628 static __always_inline u64 __cmask_word_range(const struct scx_cmask __arena *m, u32 k,
629 					      u32 nr)
630 {
631 	u64 bits = ~0ULL;
632 
633 	if (k >= nr)
634 		return 0;
635 	if (k == 0)
636 		bits &= GENMASK_U64(63, m->base & 63);
637 	if (k == nr - 1)
638 		bits &= GENMASK_U64((m->base + m->nr_cids - 1) & 63, 0);
639 	return bits;
640 }
641 
642 /*
643  * The binary operations combine cmasks of any active ranges. A cmask holds bits
644  * only for its own range and reads as the operation's identity outside it, all
645  * ones for AND and all zeros for OR, so an operand alters only the bits it
646  * covers: ANDing a shard's mask into a global one edits that shard's window and
647  * leaves the rest of the global mask alone. ANDNOT is AND with @src2 inverted
648  * over @src2's range, so where only @src2 has range the result is ~@src2.
649  *
650  * @dst is written on its range intersected with the union of the sources'
651  * ranges and untouched elsewhere, and its padding bits outside its own range
652  * stay zero, the invariant every cmask mutator keeps. @dst may alias either
653  * source, and the two sources may be one mask. Returns whether @dst has a bit
654  * set afterwards, over all of @dst's range.
655  *
656  * The operations are global functions, verified once per program. Inlined,
657  * every data-dependent branch in this loop would multiply the states the
658  * verifier explores per iteration at every call site.
659  */
660 static __always_inline bool __cmask_op(struct scx_cmask __arena *dst,
661 				       const struct scx_cmask __arena *src1,
662 				       const struct scx_cmask __arena *src2, int op)
663 {
664 	u32 d_first = dst->base / 64, d_nr = cmask_nr_words(dst);
665 	u32 s1_first = src1->base / 64, s1_nr = cmask_nr_words(src1);
666 	u32 s2_first = src2->base / 64, s2_nr = cmask_nr_words(src2);
667 	u64 any = bpf_arena_loop_zero;
668 	u32 i;
669 
670 	bpf_arena_for(i, 0, d_nr) {
671 		u32 k1 = d_first + i - s1_first, k2 = d_first + i - s2_first;
672 		u64 m1 = __cmask_word_range(src1, k1, s1_nr);
673 		u64 m2 = op == BPF_CMASK_OP_COPY ? m1 : __cmask_word_range(src2, k2, s2_nr);
674 		u64 um = __cmask_word_range(dst, i, d_nr) & (m1 | m2);
675 		u64 dv = dst->bits[i], v1, v2, rv;
676 
677 		if (um) {
678 			/* a word in range holds zeros outside the range */
679 			v1 = m1 ? src1->bits[k1] : 0;
680 			v2 = m2 ? src2->bits[k2] : 0;
681 			if (op == BPF_CMASK_OP_AND)
682 				rv = (v1 | ~m1) & (v2 | ~m2);
683 			else if (op == BPF_CMASK_OP_OR)
684 				rv = v1 | v2;
685 			else if (op == BPF_CMASK_OP_ANDNOT)
686 				rv = (v1 | ~m1) & ~v2;
687 			else
688 				rv = v1;
689 			dv = (dv & ~um) | (rv & um);
690 			dst->bits[i] = dv;
691 		}
692 		any |= dv;
693 	}
694 	return any != 0;
695 }
696 
697 /**
698  * cmask_and - Store @src1 AND @src2 in @dst
699  * @dst: destination, may be @src1 or @src2
700  * @src1: first operand
701  * @src2: second operand
702  *
703  * See __cmask_op() for how the ranges combine. Return whether @dst has a bit
704  * set afterwards.
705  */
706 __weak bool cmask_and(struct scx_cmask __arena __arg_arena *dst,
707 		      const struct scx_cmask __arena __arg_arena *src1,
708 		      const struct scx_cmask __arena __arg_arena *src2)
709 {
710 	return __cmask_op(dst, src1, src2, BPF_CMASK_OP_AND);
711 }
712 
713 /**
714  * cmask_or - Store @src1 OR @src2 in @dst
715  * @dst: destination, may be @src1 or @src2
716  * @src1: first operand
717  * @src2: second operand
718  *
719  * See __cmask_op() for how the ranges combine. Return whether @dst has a bit
720  * set afterwards.
721  */
722 __weak bool cmask_or(struct scx_cmask __arena __arg_arena *dst,
723 		     const struct scx_cmask __arena __arg_arena *src1,
724 		     const struct scx_cmask __arena __arg_arena *src2)
725 {
726 	return __cmask_op(dst, src1, src2, BPF_CMASK_OP_OR);
727 }
728 
729 /**
730  * cmask_andnot - Store @src1 AND NOT @src2 in @dst
731  * @dst: destination, may be @src1 or @src2
732  * @src1: operand to remove bits from
733  * @src2: bits to remove, inverted over its own range
734  *
735  * See __cmask_op() for how the ranges combine. Return whether @dst has a bit
736  * set afterwards.
737  */
738 __weak bool cmask_andnot(struct scx_cmask __arena __arg_arena *dst,
739 			 const struct scx_cmask __arena __arg_arena *src1,
740 			 const struct scx_cmask __arena __arg_arena *src2)
741 {
742 	return __cmask_op(dst, src1, src2, BPF_CMASK_OP_ANDNOT);
743 }
744 
745 /**
746  * cmask_copy - Copy @src into @dst
747  * @dst: destination
748  * @src: source
749  *
750  * The single-source case of __cmask_op(): @dst is written over @src's range and
751  * untouched elsewhere.
752  */
753 __weak void cmask_copy(struct scx_cmask __arena __arg_arena *dst,
754 		       const struct scx_cmask __arena __arg_arena *src)
755 {
756 	__cmask_op(dst, src, src, BPF_CMASK_OP_COPY);
757 }
758 
759 /*
760  * True iff @a and @b have identical bits over their (assumed equal) range.
761  * Callers are expected to pass same-shape cmasks; differing shapes always
762  * compare unequal.
763  */
764 static __always_inline bool cmask_equal(const struct scx_cmask __arena *a,
765 					const struct scx_cmask __arena *b)
766 {
767 	u32 nr_words, i;
768 
769 	if (a->base != b->base || a->nr_cids != b->nr_cids)
770 		return false;
771 	if (a->nr_cids == 0)
772 		return true;
773 	nr_words = (a->base + a->nr_cids - 1) / 64 - a->base / 64 + 1;
774 
775 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
776 		if (i >= nr_words)
777 			break;
778 		if (a->bits[i] != b->bits[i])
779 			return false;
780 	}
781 	return true;
782 }
783 
784 /**
785  * cmask_next_set - find the first set bit at or after @cid
786  * @m: cmask to search
787  * @cid: starting cid (clamped to @m->base if below)
788  *
789  * Returns the smallest set cid in [@cid, @m->base + @m->nr_cids), or
790  * @m->base + @m->nr_cids if none (the out-of-range sentinel matches the
791  * termination condition used by cmask_for_each()).
792  */
793 static __always_inline u32 cmask_next_set(const struct scx_cmask __arena *m, u32 cid)
794 {
795 	u32 end = m->base + m->nr_cids;
796 	u32 base = m->base / 64;
797 	u32 last_wi = (end - 1) / 64 - base;
798 	u32 start_wi, start_bit, i;
799 
800 	if (cid < m->base)
801 		cid = m->base;
802 	if (cid >= end)
803 		return end;
804 
805 	start_wi = cid / 64 - base;
806 	start_bit = cid & 63;
807 
808 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
809 		u32 wi = start_wi + i;
810 		u64 word;
811 		u32 found;
812 
813 		if (wi > last_wi)
814 			break;
815 
816 		word = m->bits[wi];
817 		if (i == 0)
818 			word &= GENMASK_U64(63, start_bit);
819 		if (!word)
820 			continue;
821 
822 		found = (base + wi) * 64 + ctzll(word);
823 		if (found >= end)
824 			return end;
825 		return found;
826 	}
827 	return end;
828 }
829 
830 static __always_inline u32 cmask_first_set(const struct scx_cmask __arena *m)
831 {
832 	return cmask_next_set(m, m->base);
833 }
834 
835 #define cmask_for_each(cid, m)							\
836 	for ((cid) = cmask_first_set(m);					\
837 	     (cid) < (m)->base + (m)->nr_cids && can_loop;			\
838 	     (cid) = cmask_next_set((m), (cid) + 1))
839 
840 /*
841  * True iff every bit set in @a is also set in @b. Matches the kernel-side
842  * scx_cmask_subset(): ranges don't need to nest, and set bits of @a outside
843  * @b's range fail the test.
844  */
845 static __always_inline bool cmask_subset(const struct scx_cmask __arena *a,
846 					 const struct scx_cmask __arena *b)
847 {
848 	u32 a_end = a->base + a->nr_cids;
849 	u32 b_end = b->base + b->nr_cids;
850 	u32 a_wbase = a->base / 64;
851 	u32 b_wbase = b->base / 64;
852 	u32 lo = a->base > b->base ? a->base : b->base;
853 	u32 hi = a_end < b_end ? a_end : b_end;
854 	u32 lo_word, hi_word, i;
855 
856 	/* set bits of @a outside @b's range can't be in @b */
857 	if (a->base < b->base &&
858 	    cmask_next_set(a, a->base) < (b->base < a_end ? b->base : a_end))
859 		return false;
860 	if (a_end > b_end &&
861 	    cmask_next_set(a, a->base > b_end ? a->base : b_end) < a_end)
862 		return false;
863 
864 	if (lo >= hi)
865 		return true;
866 
867 	/*
868 	 * Walk the words the range intersection spans. Plain word tests
869 	 * suffice: the scans above guarantee @a has no set bit outside @b's
870 	 * range and padding bits are kept clear by all cmask helpers.
871 	 */
872 	lo_word = lo / 64;
873 	hi_word = (hi - 1) / 64;
874 
875 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
876 		u32 w = lo_word + i;
877 
878 		if (w > hi_word)
879 			break;
880 		if (a->bits[w - a_wbase] & ~b->bits[w - b_wbase])
881 			return false;
882 	}
883 	return true;
884 }
885 
886 /*
887  * Population count over [base, base + nr_cids). Padding bits in the head/tail
888  * words are guaranteed zero by the mutating helpers, so a flat popcount over
889  * the words the range spans is correct.
890  */
891 static __always_inline u32 cmask_weight(const struct scx_cmask __arena *m)
892 {
893 	u32 nr_words, i;
894 	/* callers compare the sum, see bpf_arena_loop_zero */
895 	u32 count = bpf_arena_loop_zero;
896 
897 	if (!m->nr_cids)
898 		return 0;
899 	nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1;
900 
901 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
902 		if (i >= nr_words)
903 			break;
904 		count += __builtin_popcountll(m->bits[i]);
905 	}
906 	return count;
907 }
908 
909 /* true if @a and @b share any set bit, over the intersection of their ranges */
910 static __always_inline bool cmask_intersects(const struct scx_cmask __arena *a,
911 					     const struct scx_cmask __arena *b)
912 {
913 	u32 a_end = a->base + a->nr_cids;
914 	u32 b_end = b->base + b->nr_cids;
915 	u32 lo = a->base > b->base ? a->base : b->base;
916 	u32 hi = a_end < b_end ? a_end : b_end;
917 	u32 a_base = a->base / 64;
918 	u32 b_base = b->base / 64;
919 	u32 lo_word, hi_word, i;
920 	u64 head_mask, tail_mask;
921 
922 	if (lo >= hi)
923 		return false;
924 
925 	lo_word = lo / 64;
926 	hi_word = (hi - 1) / 64;
927 	head_mask = GENMASK_U64(63, lo & 63);
928 	tail_mask = GENMASK_U64((hi - 1) & 63, 0);
929 
930 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
931 		u32 w = lo_word + i;
932 		u64 mask, av, bv;
933 
934 		if (w > hi_word)
935 			break;
936 
937 		mask = GENMASK_U64(63, 0);
938 		if (w == lo_word)
939 			mask &= head_mask;
940 		if (w == hi_word)
941 			mask &= tail_mask;
942 
943 		av = a->bits[w - a_base] & mask;
944 		bv = b->bits[w - b_base] & mask;
945 		if (av & bv)
946 			return true;
947 	}
948 	return false;
949 }
950 
951 /*
952  * Find the next cid set in both @a and @b at or after @start, bounded by the
953  * intersection of the two ranges. Return a->base + a->nr_cids if none found.
954  *
955  * Building block for cmask_next_and_set_wrap(). Callers that want a bounded
956  * scan without wrap call this directly.
957  */
958 static __always_inline u32 cmask_next_and_set(const struct scx_cmask __arena *a,
959 					      const struct scx_cmask __arena *b,
960 					      u32 start)
961 {
962 	u32 a_end = a->base + a->nr_cids;
963 	u32 b_end = b->base + b->nr_cids;
964 	u32 a_wbase = a->base / 64;
965 	u32 b_wbase = b->base / 64;
966 	u32 lo = a->base > b->base ? a->base : b->base;
967 	u32 hi = a_end < b_end ? a_end : b_end;
968 	u32 last_wi, start_wi, start_bit, i;
969 
970 	if (lo >= hi)
971 		return a_end;
972 	if (start < lo)
973 		start = lo;
974 	if (start >= hi)
975 		return a_end;
976 
977 	last_wi = (hi - 1) / 64;
978 	start_wi = start / 64;
979 	start_bit = start & 63;
980 
981 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
982 		u32 abs_wi = start_wi + i;
983 		u64 word;
984 		u32 found;
985 
986 		if (abs_wi > last_wi)
987 			break;
988 
989 		word = a->bits[abs_wi - a_wbase] & b->bits[abs_wi - b_wbase];
990 		if (i == 0)
991 			word &= GENMASK_U64(63, start_bit);
992 		if (!word)
993 			continue;
994 
995 		found = abs_wi * 64 + ctzll(word);
996 		if (found >= hi)
997 			return a_end;
998 		return found;
999 	}
1000 	return a_end;
1001 }
1002 
1003 /*
1004  * Find the next set cid in @m at or after @start, wrapping to @m->base if no
1005  * set bit is found in [start, m->base + m->nr_cids). Return m->base +
1006  * m->nr_cids if @m is empty.
1007  *
1008  * Callers do round-robin distribution by passing (last_cid + 1) as @start.
1009  */
1010 static __always_inline u32 cmask_next_set_wrap(const struct scx_cmask __arena *m,
1011 					       u32 start)
1012 {
1013 	u32 end = m->base + m->nr_cids;
1014 	u32 found;
1015 
1016 	found = cmask_next_set(m, start);
1017 	if (found < end || start <= m->base)
1018 		return found;
1019 
1020 	found = cmask_next_set(m, m->base);
1021 	return found < start ? found : end;
1022 }
1023 
1024 /**
1025  * cmask_end - One past the last cid of @m's active range
1026  * @m: cmask of interest
1027  *
1028  * The scan helpers return this value when no set cid is found.
1029  */
1030 static __always_inline u32 cmask_end(const struct scx_cmask __arena *m)
1031 {
1032 	return m->base + m->nr_cids;
1033 }
1034 
1035 /*
1036  * Find the next cid set in both @a and @b at or after @start, wrapping to
1037  * @a->base if none found in the forward half. Return a->base + a->nr_cids
1038  * if the intersection is empty.
1039  *
1040  * Callers do round-robin distribution by passing (last_cid + 1) as @start.
1041  */
1042 static __always_inline u32 cmask_next_and_set_wrap(const struct scx_cmask __arena *a,
1043 						   const struct scx_cmask __arena *b,
1044 						   u32 start)
1045 {
1046 	u32 a_end = a->base + a->nr_cids;
1047 	u32 found;
1048 
1049 	found = cmask_next_and_set(a, b, start);
1050 	if (found < a_end || start <= a->base)
1051 		return found;
1052 
1053 	found = cmask_next_and_set(a, b, a->base);
1054 	return found < start ? found : a_end;
1055 }
1056 
1057 /*
1058  * The distribute helpers rotate through one slot per cid, a cacheline each so
1059  * picks on one CPU do not bounce the line of another. A missing allocation is a
1060  * setup bug and aborts the scheduler. On a CPU outside the scheduler's cid
1061  * space the helpers fall back to the first matching cid.
1062  *
1063  * TODO: The slots are one allocation without node placement. Move them to a
1064  * per-cid arena allocator once the library has one, for node-local slots
1065  * without the cacheline padding.
1066  */
1067 struct cmask_rotor_slot {
1068 	u32 cid;
1069 } __attribute__((aligned(SCX_CACHELINE_SIZE)));
1070 
1071 struct cmask_rotor_slot __arena *cmask_distribute_rotors __weak;
1072 
1073 /**
1074  * cmask_distribute_init - Allocate the distribute rotor from @map
1075  * @map: the scheduler's arena map
1076  *
1077  * The shared arena init calls this once. A scheduler with its own arena calls
1078  * it before the first distribute pick. On a kernel without the cid kfuncs
1079  * nothing can pick, so the call allocates nothing and succeeds. Return 0 on
1080  * success, -ENOMEM if the allocation fails.
1081  */
1082 static __always_inline int cmask_distribute_init(void *map)
1083 {
1084 	u64 size;
1085 	u32 pages;
1086 
1087 	/* no cid kfuncs means no picks and nothing to allocate */
1088 	if (!bpf_ksym_exists(scx_bpf_nr_cids))
1089 		return 0;
1090 
1091 	size = scx_bpf_nr_cids() * sizeof(*cmask_distribute_rotors);
1092 	pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
1093 	cmask_distribute_rotors = bpf_arena_alloc_pages(map, NULL, pages, NUMA_NO_NODE, 0);
1094 	return cmask_distribute_rotors ? 0 : -ENOMEM;
1095 }
1096 
1097 static __always_inline u32 __arena *__cmask_distribute_rotor(void)
1098 {
1099 	s32 cid = scx_bpf_this_cid();
1100 
1101 	if (unlikely(!cmask_distribute_rotors)) {
1102 		scx_bpf_error("cmask distribute rotor not allocated");
1103 		return NULL;
1104 	}
1105 	if (cid < 0)
1106 		return NULL;
1107 	return &cmask_distribute_rotors[cid].cid;
1108 }
1109 
1110 /**
1111  * cmask_any_distribute - Pick a set cid, spreading successive picks
1112  * @m: cmask to pick from
1113  *
1114  * Counterpart of bpf_cpumask_any_distribute(): a per-cid rotor makes successive
1115  * picks rotate through the set cids instead of repeating the first one. Returns
1116  * cmask_end(@m) if @m is empty.
1117  */
1118 static __always_inline u32 cmask_any_distribute(const struct scx_cmask __arena *m)
1119 {
1120 	u32 __arena *rotor = __cmask_distribute_rotor();
1121 	u32 pick;
1122 
1123 	if (unlikely(!rotor))
1124 		return cmask_first_set(m);
1125 
1126 	pick = cmask_next_set_wrap(m, *rotor + 1);
1127 	if (pick < cmask_end(m))
1128 		*rotor = pick;
1129 	return pick;
1130 }
1131 
1132 /**
1133  * cmask_any_and_distribute - Pick a cid set in both masks, spreading picks
1134  * @a: first cmask, the scan is bounded by its range
1135  * @b: second cmask
1136  *
1137  * Counterpart of bpf_cpumask_any_and_distribute(). Shares the rotor with
1138  * cmask_any_distribute() the same way the kernel counterparts share theirs.
1139  * Returns cmask_end(@a) if the intersection is empty.
1140  */
1141 static __always_inline u32 cmask_any_and_distribute(const struct scx_cmask __arena *a,
1142 						    const struct scx_cmask __arena *b)
1143 {
1144 	u32 __arena *rotor = __cmask_distribute_rotor();
1145 	u32 pick;
1146 
1147 	if (unlikely(!rotor))
1148 		return cmask_next_and_set(a, b, a->base);
1149 
1150 	pick = cmask_next_and_set_wrap(a, b, *rotor + 1);
1151 	if (pick < cmask_end(a))
1152 		*rotor = pick;
1153 	return pick;
1154 }
1155 
1156 /**
1157  * cmask_distribute_rotor_pos - Last cid picked by the distribute helpers
1158  *
1159  * For callers that anchor scans of their own on the shared rotor. Returns 0
1160  * when nothing has been picked on this cid yet or the CPU is outside the cid
1161  * space.
1162  */
1163 static __always_inline u32 cmask_distribute_rotor_pos(void)
1164 {
1165 	u32 __arena *rotor = __cmask_distribute_rotor();
1166 
1167 	if (unlikely(!rotor))
1168 		return 0;
1169 	return *rotor;
1170 }
1171 
1172 /*
1173  * Like cmask_next_and_set() but over the intersection of THREE masks. Return
1174  * a->base + a->nr_cids if no cid is set in all three at or after @start.
1175  */
1176 static __always_inline u32 cmask_next_and2_set(const struct scx_cmask __arena *a,
1177 					       const struct scx_cmask __arena *b,
1178 					       const struct scx_cmask __arena *c,
1179 					       u32 start)
1180 {
1181 	u32 a_end = a->base + a->nr_cids;
1182 	u32 b_end = b->base + b->nr_cids;
1183 	u32 c_end = c->base + c->nr_cids;
1184 	u32 a_wbase = a->base / 64;
1185 	u32 b_wbase = b->base / 64;
1186 	u32 c_wbase = c->base / 64;
1187 	u32 lo = a->base > b->base ? a->base : b->base;
1188 	u32 hi = a_end < b_end ? a_end : b_end;
1189 	u32 last_wi, start_wi, start_bit, i;
1190 
1191 	lo = lo > c->base ? lo : c->base;
1192 	hi = hi < c_end ? hi : c_end;
1193 
1194 	if (lo >= hi)
1195 		return a_end;
1196 	if (start < lo)
1197 		start = lo;
1198 	if (start >= hi)
1199 		return a_end;
1200 
1201 	last_wi = (hi - 1) / 64;
1202 	start_wi = start / 64;
1203 	start_bit = start & 63;
1204 
1205 	bpf_arena_for(i, 0, CMASK_MAX_WORDS) {
1206 		u32 abs_wi = start_wi + i;
1207 		u64 word;
1208 		u32 found;
1209 
1210 		if (abs_wi > last_wi)
1211 			break;
1212 
1213 		word = a->bits[abs_wi - a_wbase] & b->bits[abs_wi - b_wbase] &
1214 		       c->bits[abs_wi - c_wbase];
1215 		if (i == 0)
1216 			word &= GENMASK_U64(63, start_bit);
1217 		if (!word)
1218 			continue;
1219 
1220 		found = abs_wi * 64 + ctzll(word);
1221 		if (found >= hi)
1222 			return a_end;
1223 		return found;
1224 	}
1225 	return a_end;
1226 }
1227 
1228 /*
1229  * Round-robin variant of cmask_next_and2_set(): wrap to @a->base if the
1230  * three-way intersection has no cid in the forward half. Return a->base +
1231  * a->nr_cids if empty.
1232  */
1233 static __always_inline u32 cmask_next_and2_set_wrap(const struct scx_cmask __arena *a,
1234 						    const struct scx_cmask __arena *b,
1235 						    const struct scx_cmask __arena *c,
1236 						    u32 start)
1237 {
1238 	u32 a_end = a->base + a->nr_cids;
1239 	u32 found;
1240 
1241 	found = cmask_next_and2_set(a, b, c, start);
1242 	if (found < a_end || start <= a->base)
1243 		return found;
1244 
1245 	found = cmask_next_and2_set(a, b, c, a->base);
1246 	return found < start ? found : a_end;
1247 }
1248 
1249 /**
1250  * cmask_from_cpumask - translate a kernel cpumask to a cid-space cmask
1251  * @m: cmask to fill. Zeroed first; only bits within [@m->base, @m->base +
1252  *     @m->nr_cids) are updated - cpus mapping to cids outside that range
1253  *     are ignored.
1254  * @cpumask: kernel cpumask to translate
1255  *
1256  * For each cpu in @cpumask, set the cpu's cid in @m. Caller must ensure
1257  * @cpumask stays stable across the call (e.g. RCU read lock for
1258  * task->cpus_ptr).
1259  */
1260 static __always_inline void cmask_from_cpumask(struct scx_cmask __arena *m,
1261 					       const struct cpumask *cpumask)
1262 {
1263 	u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1264 	s32 cpu;
1265 
1266 	cmask_zero(m);
1267 	bpf_arena_for(cpu, 0, nr_cpu_ids) {
1268 		s32 cid;
1269 
1270 		if (!bpf_cpumask_test_cpu(cpu, cpumask))
1271 			continue;
1272 		cid = scx_bpf_cpu_to_cid(cpu);
1273 		if (cid >= 0)
1274 			__cmask_set(cid, m);
1275 	}
1276 }
1277 
1278 #endif /* __SCX_CID_BPF_H */
1279