Skip to main content

ostd/cpu/local/
dyn_cpu_local.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Dynamically-allocated CPU-local objects.
4
5use core::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull};
6
7use bitvec::prelude::{BitVec, bitvec};
8
9use super::{AnyStorage, CpuLocal};
10use crate::{
11    Result,
12    cpu::{CpuId, PinCurrentCpu, all_cpus, num_cpus},
13    irq::DisabledLocalIrqGuard,
14    mm::{FrameAllocOptions, HasPaddr, PAGE_SIZE, Segment, Vaddr, paddr_to_vaddr},
15    util::id_set::Id,
16};
17
18/// A dynamically-allocated storage for a CPU-local variable of type `T`.
19///
20/// Such a CPU-local storage should be allocated and deallocated by
21/// [`DynCpuLocalChunk`], not directly. Dropping it without deallocation
22/// will cause panic.
23///
24/// When dropping a `CpuLocal<T, DynamicStorage<T>>`, we have no way to know
25/// which `DynCpuLocalChunk` the CPU-local object was originally allocated
26/// from. Therefore, we rely on the user to correctly manage the corresponding
27/// `DynCpuLocalChunk`, ensuring that both allocation and deallocation of
28/// `CpuLocal<T, DynamicStorage<T>>` occur within the same chunk.
29///
30/// To properly deallocate the CPU-local object, the user must explicitly call
31/// the appropriate `DynCpuLocalChunk`'s `try_dealloc<T>()`. Otherwise,
32/// dropping it directly will cause a panic.
33pub struct DynamicStorage<T>(NonNull<T>);
34
35unsafe impl<T> AnyStorage<T> for DynamicStorage<T> {
36    fn get_ptr_on_current(&self, guard: &DisabledLocalIrqGuard) -> *const T {
37        self.get_ptr_on_target(guard.current_cpu())
38    }
39
40    fn get_ptr_on_target(&self, cpu_id: CpuId) -> *const T {
41        let bsp_va = self.0.as_ptr() as usize;
42        let va = bsp_va + cpu_id.as_usize() * CHUNK_SIZE;
43        va as *mut T
44    }
45}
46
47impl<T> DynamicStorage<T> {
48    /// Gets the `mut` pointer for the object on a target CPU.
49    ///
50    /// This method is intended for use when initializing or dropping the storage.
51    fn get_mut_ptr_on_target(&mut self, cpu: CpuId) -> *mut T {
52        self.get_ptr_on_target(cpu).cast_mut()
53    }
54}
55
56impl<T> Drop for DynamicStorage<T> {
57    fn drop(&mut self) {
58        panic!(
59            "Do not drop `DynamicStorage<T>` directly. \
60            Use `DynCpuLocalChunk::try_dealloc<T>` instead."
61        );
62    }
63}
64
65impl<T: Sync + alloc::fmt::Debug + 'static> alloc::fmt::Debug for CpuLocal<T, DynamicStorage<T>> {
66    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67        let mut list = f.debug_list();
68        for cpu in all_cpus() {
69            let val = self.get_on_cpu(cpu);
70            list.entry(&(&cpu, val));
71        }
72        list.finish()
73    }
74}
75
76impl<T> CpuLocal<T, DynamicStorage<T>> {
77    /// Creates a new dynamically-allocated CPU-local object, and
78    /// initializes it with `init_values`.
79    ///
80    /// The given `ptr` points to the variable located on the BSP.
81    ///
82    /// Please do not call this function directly. Instead, use
83    /// [`DynCpuLocalChunk::alloc`].
84    ///
85    /// # Safety
86    ///
87    /// The caller must ensure that
88    ///  - the new per-CPU object belongs to an existing
89    ///    [`DynCpuLocalChunk`], and does not overlap with any
90    ///    existing CPU-local object;
91    ///  - the `ITEM_SIZE` of the [`DynCpuLocalChunk`] satisfies
92    ///    the layout requirement of `T`.
93    unsafe fn __new_dynamic(ptr: *mut T, init_values: &mut impl FnMut(CpuId) -> T) -> Self {
94        let mut storage = DynamicStorage(NonNull::new(ptr).unwrap());
95        for cpu in all_cpus() {
96            let ptr = storage.get_mut_ptr_on_target(cpu);
97            // SAFETY:
98            //  - `ptr` is valid for writes, because:
99            //    - The `DynCpuLocalChunk` slot is non-null and dereferenceable.
100            //    - This initialization occurs before any other code can access
101            //      the memory. References to the data may only be created
102            //      after `Self` is created, ensuring exclusive access by the
103            //      current task.
104            //  - `ptr` is properly aligned, as the caller guarantees that the
105            //    layout requirement is satisfied.
106            unsafe {
107                core::ptr::write(ptr, init_values(cpu));
108            }
109        }
110
111        Self {
112            storage,
113            phantom: PhantomData,
114        }
115    }
116}
117
118const CHUNK_SIZE: usize = PAGE_SIZE;
119
120/// Footer metadata to describe a `SSTable`.
121#[derive(Clone, Copy, Debug)]
122struct DynCpuLocalMeta;
123crate::impl_frame_meta_for!(DynCpuLocalMeta);
124
125/// Manages dynamically-allocated CPU-local chunks.
126///
127/// Each CPU owns a chunk of size `CHUNK_SIZE`, and the chunks are laid
128/// out contiguously in the order of CPU IDs. Per-CPU variables lie within
129/// the chunks.
130pub struct DynCpuLocalChunk<const ITEM_SIZE: usize> {
131    segment: ManuallyDrop<Segment<DynCpuLocalMeta>>,
132    bitmap: BitVec,
133}
134
135impl<const ITEM_SIZE: usize> DynCpuLocalChunk<ITEM_SIZE> {
136    /// Creates a new dynamically-allocated CPU-local chunk.
137    pub fn new() -> Result<Self> {
138        let total_chunk_size = CHUNK_SIZE * num_cpus();
139        let segment = FrameAllocOptions::new()
140            .zeroed(false)
141            .alloc_segment_with(total_chunk_size.div_ceil(PAGE_SIZE), |_| DynCpuLocalMeta)?;
142
143        let num_items = CHUNK_SIZE / ITEM_SIZE;
144        const { assert!(CHUNK_SIZE.is_multiple_of(ITEM_SIZE)) };
145
146        Ok(Self {
147            segment: ManuallyDrop::new(segment),
148            bitmap: bitvec![0; num_items],
149        })
150    }
151
152    /// Returns a pointer to the local chunk owned by the BSP.
153    fn start_vaddr(&self) -> Vaddr {
154        paddr_to_vaddr(self.segment.paddr())
155    }
156
157    /// Allocates a CPU-local object from the chunk, and
158    /// initializes it with `init_values`.
159    ///
160    /// Returns `None` if the chunk is full.
161    pub fn alloc<T>(
162        &mut self,
163        init_values: &mut impl FnMut(CpuId) -> T,
164    ) -> Option<CpuLocal<T, DynamicStorage<T>>> {
165        assert!(ITEM_SIZE.is_power_of_two());
166        assert!(size_of::<T>() <= ITEM_SIZE);
167        assert!(align_of::<T>() <= ITEM_SIZE);
168
169        let index = self.bitmap.first_zero()?;
170        self.bitmap.set(index, true);
171        // SAFETY:
172        //  - `index` refers to an available position in the chunk
173        //    for allocating a new CPU-local object.
174        //  - We have checked the size and alignment requirement
175        //    for `T` above.
176        unsafe {
177            let vaddr = self.start_vaddr() + index * ITEM_SIZE;
178            Some(CpuLocal::__new_dynamic(vaddr as *mut T, init_values))
179        }
180    }
181
182    /// Gets the index of a dynamically-allocated CPU-local object
183    /// within the chunk.
184    ///
185    /// Returns `None` if the object does not belong to the chunk.
186    fn get_item_index<T>(&mut self, cpu_local: &CpuLocal<T, DynamicStorage<T>>) -> Option<usize> {
187        let vaddr = cpu_local.storage.0.as_ptr() as Vaddr;
188        let start_vaddr = self.start_vaddr();
189
190        let offset = vaddr.checked_sub(start_vaddr)?;
191        if offset > CHUNK_SIZE {
192            return None;
193        }
194
195        debug_assert_eq!(offset % ITEM_SIZE, 0);
196
197        Some(offset / ITEM_SIZE)
198    }
199
200    /// Attempts to deallocate a previously allocated CPU-local object.
201    ///
202    /// Returns `Err(cpu_local)` if the object does not belong to this chunk.
203    pub fn try_dealloc<T>(
204        &mut self,
205        mut cpu_local: CpuLocal<T, DynamicStorage<T>>,
206    ) -> Result<(), CpuLocal<T, DynamicStorage<T>>> {
207        let Some(index) = self.get_item_index(&cpu_local) else {
208            return Err(cpu_local);
209        };
210
211        self.bitmap.set(index, false);
212        for cpu in all_cpus() {
213            let ptr = cpu_local.storage.get_mut_ptr_on_target(cpu);
214            // SAFETY:
215            //  - `ptr` is valid for both reads and writes, because:
216            //    - The pointer of the CPU-local object on `cpu` is
217            //      non-null and dereferenceable.
218            //    - We can mutably borrow the CPU-local object on `cpu`
219            //      because we have the exclusive access to `cpu_local`.
220            //  - The pointer of the CPU-local object is properly aligned.
221            //  - The pointer of the CPU-local object points to a valid
222            //    instance of `T`.
223            //  - After the deallocation, no one will access the
224            //    dropped CPU-local object, since we explicitly forget
225            //    the `cpu_local`.
226            unsafe {
227                core::ptr::drop_in_place(ptr);
228            }
229        }
230        let _ = ManuallyDrop::new(cpu_local);
231        Ok(())
232    }
233
234    /// Checks whether the chunk is full.
235    pub fn is_full(&self) -> bool {
236        self.bitmap.all()
237    }
238
239    /// Checks whether the chunk is empty.
240    pub fn is_empty(&self) -> bool {
241        self.bitmap.not_any()
242    }
243}
244
245impl<const ITEM_SIZE: usize> Drop for DynCpuLocalChunk<ITEM_SIZE> {
246    fn drop(&mut self) {
247        if self.is_empty() {
248            // SAFETY: The `segment` does not contain any CPU-local objects.
249            // It is the last time the `segment` is accessed, and it will be
250            // dropped only once.
251            unsafe { ManuallyDrop::drop(&mut self.segment) }
252        } else {
253            // Leak the `segment` and panic.
254            panic!("Dropping `DynCpuLocalChunk` while some CPU-local objects are still alive");
255        }
256    }
257}