Skip to main content

ostd/cpu/
set.rs

1// SPDX-License-Identifier: MPL-2.0
2//! This module contains the implementation of the CPU set and atomic CPU set.
3use vstd::{
4    assert_seqs_equal, layout::size_of, prelude::*, set::Set, std_specs::iter::IteratorSpec,
5};
6use vstd_extra::{
7    bits::{group_u64_bit_algebra, lemma_u64_and_zero, u64_bit_is_set},
8    external::{
9        bits::{lemma_u64_set_bits_nonzero, u64_set_bits},
10        smallvec::{axiom_smallvec_from_iter_ensures, group_smallvec_models, smallvec_view},
11    },
12    ownership::Inv,
13};
14
15use super::cpu_count;
16use core::sync::atomic::{AtomicU64, Ordering};
17
18use smallvec::SmallVec;
19
20use super::{CpuId, num_cpus};
21use crate::const_assert;
22
23/// A subset of all CPUs in the system.
24#[derive(Clone, Debug, Default)]
25#[verifier::allow(autoderive_clone_without_spec)]
26#[verus_verify]
27pub struct CpuSet {
28    // A bitset representing the CPUs in the system.
29    bits: SmallVec<[InnerPart; NR_PARTS_NO_ALLOC]>,
30}
31
32type InnerPart = u64;
33
34// Original exec: `const BITS_PER_PART: usize = core::mem::size_of::<InnerPart>() * 8;`
35// literalized to `64` inside `verus!` so Verus sees a known value (exec `% BITS_PER_PART` ↔ spec `% 64`).
36#[verus_verify]
37const BITS_PER_PART: usize = 64;
38
39#[verus_verify]
40const NR_PARTS_NO_ALLOC: usize = 2;
41
42#[verus_spec(
43    returns part_idx_spec(cpu_id@),
44)]
45const fn part_idx(cpu_id: CpuId) -> usize {
46    cpu_id.as_usize() / BITS_PER_PART
47}
48
49#[verus_verify]
50#[verus_spec(
51    returns bit_idx_spec(cpu_id@),
52)]
53const fn bit_idx(cpu_id: CpuId) -> usize {
54    cpu_id.as_usize() % BITS_PER_PART
55}
56
57#[verus_verify]
58#[verifier::when_used_as_spec(parts_for_cpus_spec)]
59#[verus_spec(
60    returns parts_for_cpus_spec(num_cpus),
61)]
62const fn parts_for_cpus(num_cpus: usize) -> usize {
63    num_cpus.div_ceil(BITS_PER_PART)
64}
65
66verus! {
67
68broadcast use {
69    group_smallvec_models,
70    axiom_smallvec_from_iter_ensures,
71    vstd::std_specs::iter::group_iter_axioms,
72    lemma_u64_set_bits_nonzero,
73    group_u64_bit_algebra,
74    crate::cpu::axiom_cpu_count_bounds,
75    vstd::layout::layout_of_primitives,
76    vstd::set::group_set_lemmas,
77    vstd::set_lib::range_set_properties,
78};
79
80/// Bit `i` is set in the bit sequence `seq`.
81spec fn bit_at(seq: Seq<u64>, i: int) -> bool {
82    if 0 <= i < 64 * seq.len() {
83        u64_bit_is_set(seq[part_idx_spec(i) as int], i % 64)
84    } else {
85        false
86    }
87}
88
89impl View for CpuSet {
90    type V = Set<int>;
91
92    /// The set of CPU ids whose bit is set in `bits` (and below `cpu_count()`).
93    closed spec fn view(&self) -> Set<int> {
94        Set::range(0, cpu_count()).filter(|i: int| bit_at(smallvec_view(&self.bits), i))
95    }
96}
97
98impl CpuSet {
99    /// Number of set bits in the backing words.
100    pub closed spec fn count_spec(&self) -> int {
101        count_set_bits(smallvec_view(&self.bits))
102    }
103
104    /// Number of backing words.
105    pub closed spec fn num_parts(&self) -> nat {
106        smallvec_view(&self.bits).len()
107    }
108}
109
110impl Inv for CpuSet {
111    /// The backing vector holds exactly `parts_for_cpus(cpu_count)` words, the view
112    /// is bounded by `cpu_count`, and the unused tail bits (>= `cpu_count`) are clear.
113    closed spec fn inv(self) -> bool {
114        &&& smallvec_view(&self.bits).len() == parts_for_cpus_spec(cpu_count() as usize)
115        &&& forall|j: int|
116            #![trigger bit_at(smallvec_view(&self.bits), j)]
117            cpu_count() <= j < 64 * smallvec_view(&self.bits).len() ==> !bit_at(
118                smallvec_view(&self.bits),
119                j,
120            )
121    }
122}
123
124} // verus!
125#[verus_verify]
126impl CpuSet {
127    /// Creates a new `CpuSet` with all CPUs in the system.
128    #[verus_spec(ret =>
129        ensures
130            ret@ == Set::range(0, cpu_count()),
131            ret.inv(),
132    )]
133    pub fn new_full() -> Self {
134        let mut ret = Self::with_capacity_val(num_cpus(), !0);
135        ret.clear_nonexistent_cpu_bits();
136        ret
137    }
138
139    /// Creates a new `CpuSet` with no CPUs in the system.
140    #[verus_spec(ret =>
141        ensures
142            ret@ == Set::empty(),
143            ret.inv(),
144    )]
145    pub fn new_empty() -> Self {
146        let ret = Self::with_capacity_val(num_cpus(), 0);
147        proof! {
148            lemma_empty_bits_imply_empty_set(&ret);
149            assert forall|j: int|
150                cpu_count() <= j < 64 * smallvec_view(&ret.bits).len() implies !bit_at(
151                    smallvec_view(&ret.bits),
152                    j,
153                ) by {
154                lemma_u64_and_zero(1u64 << bit_idx_spec(j));
155            }
156        }
157        ret
158    }
159
160    /// Adds a CPU to the set.
161    #[verus_spec(
162        requires
163            self.inv(),
164        ensures
165            final(self)@ == old(self)@.insert(cpu_id@),
166            final(self).inv(),
167    )]
168    pub fn add(&mut self, cpu_id: CpuId) {
169        proof! {
170            use_type_invariant(&cpu_id);
171        }
172        let part_idx = part_idx(cpu_id);
173        let bit_idx = bit_idx(cpu_id);
174        if part_idx >= self.bits.len() {
175            self.bits.resize(part_idx + 1, 0);
176        }
177        self.bits[part_idx] |= 1 << bit_idx;
178        proof! {
179            let old_seq = smallvec_view(&old(self).bits);
180            let new_seq = smallvec_view(&self.bits);
181            let len = old_seq.len();
182            assert forall|j: int| cpu_count() <= j < 64 * len implies !bit_at(new_seq, j) by {
183                assert(bit_at(new_seq, j) == bit_at(old_seq, j));
184            }
185        }
186    }
187
188    /// Removes a CPU from the set.
189    #[verus_spec(
190        requires
191            self.inv(),
192        ensures
193            final(self)@ == old(self)@.remove(cpu_id@),
194            final(self).inv(),
195    )]
196    pub fn remove(&mut self, cpu_id: CpuId) {
197        let part_idx = part_idx(cpu_id);
198        let bit_idx = bit_idx(cpu_id);
199        if part_idx < self.bits.len() {
200            self.bits[part_idx] &= !(1 << bit_idx);
201            proof! {
202                let old_seq = smallvec_view(&old(self).bits);
203                let new_seq = smallvec_view(&self.bits);
204                assert forall|j: int| cpu_count() <= j < 64 * old_seq.len() implies !bit_at(
205                    new_seq,
206                    j,
207                ) by {
208                    assert(bit_at(new_seq, j) == bit_at(old_seq, j));
209                }
210            }
211        }
212    }
213
214    /// Returns true if the set contains the specified CPU.
215    #[verus_spec(ret =>
216        requires
217            self.inv(),
218        returns self@.contains(cpu_id@),
219    )]
220    pub fn contains(&self, cpu_id: CpuId) -> bool {
221        proof! {
222            use_type_invariant(&cpu_id);
223        }
224        let part_idx = part_idx(cpu_id);
225        let bit_idx = bit_idx(cpu_id);
226        part_idx < self.bits.len() && (self.bits[part_idx] & (1 << bit_idx)) != 0
227    }
228
229    /// Returns the number of CPUs in the set.
230    #[verus_spec(
231        requires
232            self.inv(),
233        returns self.count_spec() as usize,
234    )]
235    pub fn count(&self) -> usize {
236        /* `Iterator::sum` has no model in the active vstd, so use an indexed loop with a
237         * prefix-sum invariant while preserving the same word order and arithmetic.
238         * Origin Rust: self.bits
239         *     .iter()
240         *     .map(|part| part.count_ones() as usize)
241         *     .sum()
242         */
243        let mut count = 0usize;
244        let mut idx = 0usize;
245        #[verus_spec(
246            invariant
247                self.inv(),
248                idx <= smallvec_view(&self.bits).len(),
249                count == smallvec_view(&self.bits)[..idx].fold_left(
250                    0,
251                    |count: int, word: u64| count + u64_set_bits(word),
252                ),
253                count <= 64 * idx,
254            decreases
255                smallvec_view(&self.bits).len() - idx,
256        )]
257        while idx < self.bits.len() {
258            let part = self.bits[idx];
259            let part_count = part.count_ones() as usize;
260            proof! {
261                let seq = smallvec_view(&self.bits);
262                assert_seqs_equal!(
263                    seq[..idx + 1].drop_last() == seq[..idx]
264                );
265            }
266            count += part_count;
267            idx += 1;
268        }
269        count
270    }
271
272    /// Returns true if the set is empty.
273    #[verus_spec(ret =>
274        requires
275            self.inv(),
276        returns self@ == Set::empty(),
277    )]
278    pub fn is_empty(&self) -> bool {
279        /* Bind the result as `ret` so the spec and the proof below can refer to it.
280         * Origin Rust: self.bits.iter().all(|part| *part == 0)
281         */
282        let ret = self.bits.iter().all(
283            #[verus_spec(ret: bool => ensures ret == (*part == 0u64))]
284            |part| *part == 0,
285        );
286        proof! {
287            let seq = smallvec_view(&self.bits);
288            if ret {
289                assert forall|i: int|
290                    0 <= i < smallvec_view(&self.bits).len() implies
291                        smallvec_view(&self.bits)[i] == 0u64 by {
292                    assert(*seq.as_ref()[i] == seq[i]);
293                }
294                lemma_empty_bits_imply_empty_set(self);
295            } else {
296                let idx = choose|i: int| 0 <= i < seq.len() && seq[i] != 0u64;
297                lemma_u64_nonzero_has_set_bit(seq[idx]);
298                let b = choose|b: int| 0 <= b < 64 && #[trigger] u64_bit_is_set(seq[idx], b);
299                let j = 64 * idx + b;
300                assert(bit_at(seq, j));
301                assert(self@.contains(j));
302            }
303        }
304        ret
305    }
306
307    /// Returns true if the set is full.
308    #[verus_spec(ret =>
309        requires
310            self.inv(),
311        returns self@ == Set::range(0, cpu_count()),
312    )]
313    pub fn is_full(&self) -> bool {
314        /* `Enumerate` has no `IteratorSpecImpl` in the active vstd, so use an indexed loop
315         * with the same ascending word order and the same first-mismatch short circuit.
316         * Origin Rust: let num_cpus = num_cpus();
317         * self.bits.iter().enumerate().all(|(idx, part)| {
318         *     if idx == self.bits.len() - 1 && num_cpus % BITS_PER_PART != 0 {
319         *         *part == (1 << (num_cpus % BITS_PER_PART)) - 1
320         *     } else {
321         *         *part == !0
322         *     }
323         * })
324         */
325        let num_cpus = num_cpus();
326        let mut idx = 0usize;
327        #[verus_spec(
328            invariant
329                self.inv(),
330                num_cpus == cpu_count(),
331                idx <= smallvec_view(&self.bits).len(),
332                forall|i: int|
333                    #![trigger smallvec_view(&self.bits)[i]]
334                    0 <= i < idx ==> smallvec_view(&self.bits)[i]
335                        == full_set_word(
336                            cpu_count(),
337                            smallvec_view(&self.bits).len() as int,
338                            i,
339                        ),
340            decreases
341                smallvec_view(&self.bits).len() - idx,
342        )]
343        while idx < self.bits.len() {
344            let expected = if idx == self.bits.len() - 1 && num_cpus % BITS_PER_PART != 0 {
345                (1 << (num_cpus % BITS_PER_PART)) - 1
346            } else {
347                !0
348            };
349            if self.bits[idx] != expected {
350                proof! {
351                    lemma_mismatched_word_imply_not_full_set(self, idx as int);
352                }
353                return false;
354            }
355            idx += 1;
356        }
357        proof! {
358            lemma_full_bits_imply_full_set(self);
359        }
360        true
361    }
362
363    /// Adds all CPUs to the set.
364    #[verus_spec(
365        requires
366            self.inv(),
367        ensures
368            final(self)@ == Set::range(0, cpu_count()),
369            final(self).inv(),
370    )]
371    pub fn add_all(&mut self) {
372        self.bits.fill(!0);
373        self.clear_nonexistent_cpu_bits();
374    }
375
376    /// Removes all CPUs from the set.
377    #[verus_spec(
378        requires
379            self.inv(),
380        ensures
381            final(self)@ == Set::empty(),
382            final(self).inv(),
383    )]
384    pub fn clear(&mut self) {
385        self.bits.fill(0);
386        proof! {
387            lemma_empty_bits_imply_empty_set(self);
388            assert forall|j: int|
389                cpu_count() <= j < 64 * smallvec_view(&self.bits).len() implies !bit_at(
390                    smallvec_view(&self.bits),
391                    j,
392                ) by {
393                lemma_u64_and_zero(1u64 << bit_idx_spec(j));
394            }
395        }
396    }
397
398    /// Iterates over the CPUs in the set.
399    ///
400    /// The order of the iteration is guaranteed to be in ascending order.
401    #[verus_spec(ret =>
402        requires
403            self.inv(),
404        ensures
405            IteratorSpec::obeys_prophetic_iter_laws(&ret),
406            IteratorSpec::decrease(&ret) is Some,
407    )]
408    pub fn iter(&self) -> impl Iterator<Item = CpuId> + '_ {
409        /* `Enumerate`, `FlatMap`, and `FilterMap` are not modeled by the active vstd, so scan
410         * the same bit positions with modeled `Filter` and `Map` adapters. The position order
411         * and selected CPU IDs are unchanged.
412         * Origin Rust: self.bits.iter().enumerate().flat_map(|(part_idx, &part)| {
413         *     (0..BITS_PER_PART).filter_map(move |bit_idx| {
414         *         if (part & (1 << bit_idx)) != 0 {
415         *             let id = part_idx * BITS_PER_PART + bit_idx;
416         *             Some(CpuId(id as u32))
417         *         } else {
418         *             None
419         *         }
420         *     })
421         * })
422         */
423        let end = self.bits.len() * BITS_PER_PART;
424        (0..end)
425            .filter(
426                #[verus_spec(ret: bool =>
427                    requires
428                        *id < end,
429                    ensures
430                        ret == bit_at(smallvec_view(&self.bits), *id as int),
431                )]
432                move |id| {
433                    let part_idx = *id / BITS_PER_PART;
434                    let bit_idx = *id % BITS_PER_PART;
435                    (self.bits[part_idx] & (1 << bit_idx)) != 0
436                },
437            )
438            .map(
439                #[verus_spec(ret: CpuId =>
440                    requires
441                        bit_at(smallvec_view(&self.bits), id as int),
442                    ensures
443                        ret == CpuId(id as u32),
444                )]
445                move |id| CpuId(id as u32),
446            )
447    }
448
449    /// Only for internal use. The set cannot contain non-existent CPUs.
450    #[verus_spec(ret =>
451        requires
452            num_cpus == cpu_count(),
453            2 * parts_for_cpus_spec(num_cpus) * size_of::<u64>() <= isize::MAX,
454        ensures
455            smallvec_view(&ret.bits).len() == parts_for_cpus_spec(num_cpus),
456        forall|i: int|
457            #![trigger smallvec_view(&ret.bits)[i]]
458            0 <= i < smallvec_view(&ret.bits).len() ==> smallvec_view(&ret.bits)[i]
459                == val,
460    )]
461    fn with_capacity_val(num_cpus: usize, val: InnerPart) -> Self {
462        let num_parts = parts_for_cpus(num_cpus);
463        let mut bits = SmallVec::with_capacity(num_parts);
464        bits.resize(num_parts, val);
465        Self { bits }
466    }
467
468    #[verus_spec(
469        requires
470            smallvec_view(&self.bits).len() == parts_for_cpus_spec(cpu_count() as usize),
471            forall|j: int|
472                #![trigger bit_at(smallvec_view(&self.bits), j)]
473                0 <= j < cpu_count() ==> bit_at(smallvec_view(&self.bits), j),
474        ensures
475            final(self).inv(),
476            forall|j: int|
477                #![trigger bit_at(smallvec_view(&final(self).bits), j)]
478                0 <= j < cpu_count() ==> bit_at(smallvec_view(&final(self).bits), j),
479    )]
480    fn clear_nonexistent_cpu_bits(&mut self) {
481        let num_cpus = num_cpus();
482        if num_cpus % BITS_PER_PART != 0 {
483            let num_parts = parts_for_cpus(num_cpus);
484            self.bits[num_parts - 1] &= (1 << (num_cpus % BITS_PER_PART)) - 1;
485            proof! {
486                let n = cpu_count();
487                let old_seq = smallvec_view(&old(self).bits);
488                let new_seq = smallvec_view(&self.bits);
489                assert forall|j: int| 0 <= j < n implies
490                    bit_at(new_seq, j) == bit_at(old_seq, j) by {}
491            }
492        }
493    }
494}
495
496/* impl From<CpuId> for CpuSet {
497    fn from(cpu_id: CpuId) -> Self {
498        let mut set = Self::new_empty();
499        set.add(cpu_id);
500        set
501    }
502} */
503
504/// A subset of all CPUs in the system with atomic operations.
505///
506/// It provides atomic operations for each CPU in the system. When the
507/// operation contains multiple CPUs, the ordering is not guaranteed.
508#[derive(Debug)]
509#[verus_verify]
510pub struct AtomicCpuSet {
511    bits: SmallVec<[AtomicInnerPart; NR_PARTS_NO_ALLOC]>,
512}
513
514type AtomicInnerPart = AtomicU64;
515/* const_assert!(core::mem::size_of::<AtomicInnerPart>() * 8 == BITS_PER_PART); */
516
517verus! {
518
519impl AtomicCpuSet {
520    /// Number of backing words.
521    pub closed spec fn num_parts(&self) -> nat {
522        smallvec_view(&self.bits).len()
523    }
524
525    /// The word count always matches the system's CPU count.
526    #[verifier::type_invariant]
527    closed spec fn type_inv(self) -> bool {
528        Self::num_parts(&self) == parts_for_cpus_spec(cpu_count() as usize)
529    }
530}
531
532} // verus!
533#[verus_verify]
534impl AtomicCpuSet {
535    /// Creates a new `AtomicCpuSet` with an initial value.
536    #[verus_spec(requires value.inv())]
537    pub fn new(value: CpuSet) -> Self {
538        let bits = value.bits.into_iter().map(AtomicU64::new).collect();
539        Self { bits }
540    }
541
542    /// Loads the value of the set with the given ordering.
543    ///
544    /// This operation is not atomic. When racing with a [`Self::store`]
545    /// operation, this load may return a set that contains a portion of the
546    /// new value and a portion of the old value. Load on each specific
547    /// word is atomic, and follows the specified ordering.
548    ///
549    /// Note that load with [`Ordering::Release`] is a valid operation, which
550    /// is different from the normal atomic operations. When coupled with
551    /// [`Ordering::Release`], it actually performs `fetch_or(0, Release)`.
552    #[verus_spec(ret =>
553        requires
554            ordering != Ordering::AcqRel,
555        ensures
556            ret.num_parts() == self.num_parts(),
557    )]
558    pub fn load(&self, ordering: Ordering) -> CpuSet {
559        let bits = self
560            .bits
561            .iter()
562            .map(|part| match ordering {
563                Ordering::Release => part.fetch_or(0, ordering),
564                _ => part.load(ordering),
565            })
566            .collect();
567        CpuSet { bits }
568    }
569
570    /// Stores a new value to the set with the given ordering.
571    ///
572    /// This operation is not atomic. When racing with a [`Self::load`]
573    /// operation, that load may return a set that contains a portion of the
574    /// new value and a portion of the old value. Load on each specific
575    /// word is atomic, and follows the specified ordering.
576    #[verus_spec(
577        requires
578            ordering != Ordering::Acquire,
579            ordering != Ordering::AcqRel,
580    )]
581    pub fn store(&self, value: &CpuSet, ordering: Ordering) {
582        #[verus_spec(
583            invariant
584                ordering != Ordering::Acquire,
585                ordering != Ordering::AcqRel,
586        )]
587        for (part, new_part) in self.bits.iter().zip(value.bits.iter()) {
588            part.store(*new_part, ordering);
589        }
590    }
591
592    /// Atomically adds a CPU with the given ordering.
593    pub fn add(&self, cpu_id: CpuId, ordering: Ordering) {
594        let part_idx = part_idx(cpu_id);
595        let bit_idx = bit_idx(cpu_id);
596        if part_idx < self.bits.len() {
597            self.bits[part_idx].fetch_or(1 << bit_idx, ordering);
598        }
599    }
600
601    /// Atomically removes a CPU with the given ordering.
602    pub fn remove(&self, cpu_id: CpuId, ordering: Ordering) {
603        let part_idx = part_idx(cpu_id);
604        let bit_idx = bit_idx(cpu_id);
605        if part_idx < self.bits.len() {
606            self.bits[part_idx].fetch_and(!(1 << bit_idx), ordering);
607        }
608    }
609
610    /// Atomically checks if the set contains the specified CPU.
611    #[verus_spec(ret =>
612        requires
613            ordering != Ordering::Release,
614            ordering != Ordering::AcqRel,
615        ensures
616            cpu_id@ / 64 >= self.num_parts() ==> ret == false,
617    )]
618    pub fn contains(&self, cpu_id: CpuId, ordering: Ordering) -> bool {
619        let part_idx = part_idx(cpu_id);
620        let bit_idx = bit_idx(cpu_id);
621        part_idx < self.bits.len() && (self.bits[part_idx].load(ordering) & (1 << bit_idx)) != 0
622    }
623}
624
625// Private auxiliary specifications and proof lemmas backing the implementations above. They are
626// collected at the end of the file so that the APIs and critical proofs stay in focus.
627
628verus! {
629
630/// `div_ceil(n, BITS_PER_PART)`: the number of 64-bit words needed to hold `n` bits.
631spec fn parts_for_cpus_spec(n: usize) -> usize {
632    if n == 0 {
633        0
634    } else {
635        ((n + 63) / 64) as usize
636    }
637}
638
639/// The 64-bit word holding bit `i`.
640spec fn part_idx_spec(i: int) -> usize {
641    (i / 64) as usize
642}
643
644/// The position of bit `i` within its 64-bit word.
645spec fn bit_idx_spec(i: int) -> usize {
646    (i % 64) as usize
647}
648
649/// Number of set bits in all words of `seq`.
650spec fn count_set_bits(seq: Seq<u64>) -> int {
651    seq.fold_left(0, |count: int, word: u64| count + u64_set_bits(word))
652}
653
654/// Expected value of word `idx` in a full CPU set.
655spec fn full_set_word(num_cpus: int, len: int, idx: int) -> u64 {
656    if idx == len - 1 && bit_idx_spec(num_cpus) != 0 {
657        ((1u64 << bit_idx_spec(num_cpus)) - 1) as u64
658    } else {
659        !0u64
660    }
661}
662
663proof fn lemma_u64_nonzero_has_set_bit_aux(word: u64, n: int)
664    requires
665        word != 0,
666        0 <= n <= 64,
667        word >> (n as u32) == 0,
668    ensures
669        exists|b: int| 0 <= b < n && #[trigger] u64_bit_is_set(word, b),
670    decreases n,
671{
672    if n == 0 {
673        assert(word >> (0u32) == word) by (bit_vector);
674    } else if !u64_bit_is_set(word, 0) {
675        let shifted = word >> 1u32;
676        assert(shifted != 0) by (bit_vector)
677            requires
678                shifted == word >> 1u32,
679                word != 0,
680                (word & (1u64 << 0usize)) == 0u64,
681        ;
682        let prev: int = n - 1;
683        let nu: u32 = n as u32;
684        let prevu: u32 = (n - 1) as u32;
685        assert(shifted >> prevu == word >> nu) by (bit_vector)
686            requires
687                shifted == word >> 1u32,
688                nu == prevu + 1,
689                nu <= 64,
690        ;
691        lemma_u64_nonzero_has_set_bit_aux(shifted, prev);
692        let b = choose|b: int| 0 <= b < n - 1 && #[trigger] u64_bit_is_set(shifted, b);
693        let bu: u32 = b as u32;
694        let next: u32 = (b + 1) as u32;
695        assert(u64_bit_is_set(word, b + 1)) by {
696            assert((word & (1u64 << next)) != 0) by (bit_vector)
697                requires
698                    shifted == word >> 1u32,
699                    (shifted & (1u64 << bu)) != 0,
700                    next == bu + 1,
701                    bu < 63,
702            ;
703        };
704    }
705}
706
707proof fn lemma_u64_nonzero_has_set_bit(word: u64)
708    requires
709        word != 0,
710    ensures
711        exists|b: int| 0 <= b < 64 && #[trigger] u64_bit_is_set(word, b),
712{
713    assert(word >> 64u32 == 0) by (bit_vector);
714    lemma_u64_nonzero_has_set_bit_aux(word, 64);
715}
716
717/// A CPU set whose backing words are all zero has an empty abstract view.
718proof fn lemma_empty_bits_imply_empty_set(set: &CpuSet)
719    requires
720        smallvec_view(&set.bits).all(|w: u64| w == 0u64),
721    ensures
722        set@ == Set::empty(),
723{
724    let seq = smallvec_view(&set.bits);
725    assert forall|k: int| 0 <= k < seq.len() implies seq[k] == 0u64 by {
726        let p = |w: u64| w == 0u64;
727        assert(p(seq[k]));
728    }
729    assert forall|j: int| !set@.contains(j) by {
730        if 0 <= j < cpu_count() && j < 64 * seq.len() {
731            lemma_u64_and_zero(1u64 << bit_idx_spec(j));
732        }
733    }
734}
735
736/// If every backing word has the full-set value, every existing CPU bit is set.
737proof fn lemma_full_bits_imply_full_set(set: &CpuSet)
738    requires
739        set.inv(),
740        forall|i: int|
741            #![trigger smallvec_view(&set.bits)[i]]
742            0 <= i < smallvec_view(&set.bits).len() ==> smallvec_view(&set.bits)[i]
743                == full_set_word(cpu_count(), smallvec_view(&set.bits).len() as int, i),
744    ensures
745        set@ == Set::range(0, cpu_count()),
746{
747    let seq = smallvec_view(&set.bits);
748    let n = cpu_count();
749    let len = seq.len();
750    assert forall|a: int|
751        #![trigger set@.contains(a)]
752        set@.contains(a) == Set::range(0, n).contains(a) by {
753        if 0 <= a < n {
754            let p = a / 64;
755            if a / 64 == len - 1 && n % 64 != 0 {
756                let mask = seq[p];
757                // Materialize the `!0u64 & mask` term: the broadcast masked-bit lemmas
758                // then chain (with `!0u64` as the word) to show that every in-range bit
759                // of the partial last word is set.
760                assert(!0u64 & mask == mask) by (bit_vector);
761            }
762        }
763    }
764}
765
766/// Two distinct `u64` words differ at some unit bit.
767proof fn lemma_u64_distinct_bits_differ(x: u64, y: u64)
768    requires
769        x != y,
770    ensures
771        exists|b: int|
772            0 <= b < 64 && (#[trigger] (x & (1u64 << (b as usize)))) != (y & (1u64 << (
773            b as usize))),
774{
775    let diff: u64 = x ^ y;
776    assert(diff != 0u64) by (bit_vector)
777        requires
778            diff == x ^ y,
779            x != y,
780    ;
781    lemma_u64_nonzero_has_set_bit(diff);
782    let b = choose|b: int| 0 <= b < 64 && #[trigger] u64_bit_is_set(diff, b);
783    let bu: u32 = b as u32;
784    assert((x & (1u64 << bu)) != (y & (1u64 << bu))) by (bit_vector)
785        requires
786            (diff & (1u64 << bu)) != 0,
787            diff == x ^ y,
788    ;
789}
790
791/// ANDing with the unit bit at `b` yields `0` or the unit bit itself.
792proof fn lemma_u64_unit_bit_projection(x: u64, b: int)
793    requires
794        0 <= b < 64,
795    ensures
796        !u64_bit_is_set(x, b) || ((x & (1u64 << (b as usize))) == (1u64 << (b as usize))),
797{
798    let bu: u32 = b as u32;
799    assert((x & (1u64 << bu)) == 0u64 || (x & (1u64 << bu)) == (1u64 << bu)) by (bit_vector);
800}
801
802/// If some backing word differs from its full-set value, the abstract view is not
803/// the full CPU range: the differing bit is either a missing CPU bit (below
804/// `cpu_count`), or a set nonexistent bit, which `inv` rules out.
805proof fn lemma_mismatched_word_imply_not_full_set(set: &CpuSet, p: int)
806    requires
807        set.inv(),
808        0 <= p < smallvec_view(&set.bits).len(),
809        smallvec_view(&set.bits)[p] != full_set_word(
810            cpu_count(),
811            smallvec_view(&set.bits).len() as int,
812            p,
813        ),
814    ensures
815        set@ != Set::range(0, cpu_count()),
816{
817    let seq = smallvec_view(&set.bits);
818    let n = cpu_count();
819    let len = seq.len() as int;
820    let k = n % 64;
821    // A bit where the word differs from its full-set value.
822    let word = seq[p];
823    let expected = full_set_word(n, len, p);
824    lemma_u64_distinct_bits_differ(word, expected);
825    let b = choose|b: int|
826        0 <= b < 64 && (#[trigger] (word & (1u64 << (b as usize)))) != (expected & (1u64 << (
827        b as usize)));
828    lemma_u64_unit_bit_projection(word, b);
829    lemma_u64_unit_bit_projection(expected, b);
830    let j = 64 * p + b;
831    assert(set@.contains(j) == (0 <= j < n && bit_at(seq, j)));
832    if p == len - 1 && k != 0 {
833        if u64_bit_is_set(expected, b) {
834            // The mask only keeps bits below `k`, so `j` is a missing CPU bit.
835            if b >= k {
836                assert(!0u64 & expected == expected) by (bit_vector);
837                assert(false);
838            }
839        } else {
840            // A set bit at or above `n` would violate the clear-tail invariant.
841            if b < k {
842                assert(!0u64 & expected == expected) by (bit_vector);
843                assert(false);
844            }
845            assert(bit_at(seq, j));
846            assert(false);
847        }
848    }
849}
850
851} // verus!
852#[cfg(ktest)]
853mod test {
854    use super::*;
855    use crate::{cpu::all_cpus, prelude::*};
856
857    #[ktest]
858    fn test_full_cpu_set_iter_is_all() {
859        let set = CpuSet::new_full();
860        let num_cpus = num_cpus();
861        let all_cpus = all_cpus().collect::<Vec<_>>();
862        let set_cpus = set.iter().collect::<Vec<_>>();
863
864        assert!(set_cpus.len() == num_cpus);
865        assert_eq!(set_cpus, all_cpus);
866    }
867
868    #[ktest]
869    fn test_full_cpu_set_contains_all() {
870        let set = CpuSet::new_full();
871        for cpu_id in all_cpus() {
872            assert!(set.contains(cpu_id));
873        }
874    }
875
876    #[ktest]
877    fn test_empty_cpu_set_iter_is_empty() {
878        let set = CpuSet::new_empty();
879        let set_cpus = set.iter().collect::<Vec<_>>();
880        assert!(set_cpus.is_empty());
881    }
882
883    #[ktest]
884    fn test_empty_cpu_set_contains_none() {
885        let set = CpuSet::new_empty();
886        for cpu_id in all_cpus() {
887            assert!(!set.contains(cpu_id));
888        }
889    }
890
891    #[ktest]
892    fn test_atomic_cpu_set_multiple_sizes() {
893        for test_num_cpus in [1usize, 3, 12, 64, 96, 99, 128, 256, 288, 1024] {
894            let test_all_iter = || (0..test_num_cpus).map(|id| CpuId(id as u32));
895
896            let set = CpuSet::with_capacity_val(test_num_cpus, 0);
897            let atomic_set = AtomicCpuSet::new(set);
898
899            for cpu_id in test_all_iter() {
900                assert!(!atomic_set.contains(cpu_id, Ordering::Relaxed));
901                if cpu_id.as_usize() % 3 == 0 {
902                    atomic_set.add(cpu_id, Ordering::Relaxed);
903                }
904            }
905
906            let loaded = atomic_set.load(Ordering::Relaxed);
907            for cpu_id in loaded.iter() {
908                if cpu_id.as_usize() % 3 == 0 {
909                    assert!(loaded.contains(cpu_id));
910                } else {
911                    assert!(!loaded.contains(cpu_id));
912                }
913            }
914
915            atomic_set.store(
916                &CpuSet::with_capacity_val(test_num_cpus, 0),
917                Ordering::Relaxed,
918            );
919
920            for cpu_id in test_all_iter() {
921                assert!(!atomic_set.contains(cpu_id, Ordering::Relaxed));
922                atomic_set.add(cpu_id, Ordering::Relaxed);
923            }
924        }
925    }
926}