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