nexus_slab/byte/
unbounded.rs1use core::marker::PhantomData;
4use core::mem;
5
6use crate::shared::SlotCell;
7
8use super::{AlignedBytes, Slot, validate_type};
9
10pub struct Slab<const N: usize> {
37 inner: crate::unbounded::Slab<AlignedBytes<N>>,
38}
39
40impl<const N: usize> Slab<N> {
41 #[inline]
51 pub unsafe fn with_chunk_capacity(chunk_capacity: usize) -> Self {
52 unsafe { Builder::new().chunk_capacity(chunk_capacity).build::<N>() }
54 }
55
56 #[inline]
63 pub fn alloc<T>(&self, value: T) -> Slot<T> {
64 validate_type::<T, N>();
65
66 let (slot_ptr, _chunk_idx) = self.inner.claim_ptr();
67
68 unsafe {
71 let data_ptr = slot_ptr.cast::<T>();
72 core::ptr::write(data_ptr, value);
73 }
74
75 Slot {
76 ptr: slot_ptr.cast::<u8>(),
77 _marker: PhantomData,
78 }
79 }
80
81 #[inline]
87 pub fn claim(&self) -> super::ByteClaim<'_> {
88 let claim = self.inner.claim();
89 let (ptr, chunk_idx) = claim.into_ptr();
90 let slab_ptr = core::ptr::from_ref(&self.inner).cast::<u8>();
91 unsafe {
93 super::ByteClaim::from_raw_parts(
94 ptr.cast::<u8>(),
95 slab_ptr,
96 free_raw_impl::<N>,
97 chunk_idx,
98 N,
99 )
100 }
101 }
102
103 #[inline]
109 pub unsafe fn free_raw(&self, ptr: *mut u8) {
110 unsafe {
111 self.inner.free_ptr(ptr.cast());
112 }
113 }
114
115 #[inline]
121 pub unsafe fn free_raw_in_chunk(&self, ptr: *mut u8, chunk_idx: usize) {
122 unsafe {
123 self.inner.free_ptr_in_chunk(ptr.cast(), chunk_idx);
124 }
125 }
126
127 #[inline]
138 pub unsafe fn alloc_raw(&self, src: *const u8, size: usize) -> *mut u8 {
139 assert!(size <= N, "raw alloc size {size} exceeds slot size {N}");
140 let (slot_ptr, _chunk_idx) = self.inner.claim_ptr();
141 let dst = slot_ptr.cast::<u8>();
142 unsafe { core::ptr::copy_nonoverlapping(src, dst, size) };
143 dst
144 }
145
146 #[inline]
150 pub fn free<T>(&self, ptr: Slot<T>) {
151 let data_ptr = ptr.ptr;
152 debug_assert!(
153 self.inner.contains_ptr(data_ptr as *const ()),
154 "slot was not allocated from this slab"
155 );
156 mem::forget(ptr);
157
158 unsafe {
159 core::ptr::drop_in_place(data_ptr.cast::<T>());
160 self.inner
161 .free_ptr(data_ptr.cast::<SlotCell<AlignedBytes<N>>>());
162 }
163 }
164
165 #[inline]
169 pub fn take<T>(&self, ptr: Slot<T>) -> T {
170 let data_ptr = ptr.ptr;
171 debug_assert!(
172 self.inner.contains_ptr(data_ptr as *const ()),
173 "slot was not allocated from this slab"
174 );
175 mem::forget(ptr);
176
177 unsafe {
178 let value = core::ptr::read(data_ptr.cast::<T>());
179 self.inner
180 .free_ptr(data_ptr.cast::<SlotCell<AlignedBytes<N>>>());
181 value
182 }
183 }
184}
185
186pub struct Builder {
213 chunk_capacity: usize,
214 initial_chunks: usize,
215}
216
217impl Builder {
218 #[inline]
222 pub fn new() -> Self {
223 Self {
224 chunk_capacity: 256,
225 initial_chunks: 0,
226 }
227 }
228
229 #[inline]
235 pub fn chunk_capacity(mut self, cap: usize) -> Self {
236 self.chunk_capacity = cap;
237 self
238 }
239
240 #[inline]
244 pub fn initial_chunks(mut self, n: usize) -> Self {
245 self.initial_chunks = n;
246 self
247 }
248
249 #[inline]
259 pub unsafe fn build<const N: usize>(self) -> Slab<N> {
260 let inner = unsafe {
262 crate::unbounded::Builder::new()
263 .chunk_capacity(self.chunk_capacity)
264 .initial_chunks(self.initial_chunks)
265 .build::<AlignedBytes<N>>()
266 };
267 Slab { inner }
268 }
269}
270
271impl Default for Builder {
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277impl core::fmt::Debug for Builder {
278 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
279 f.debug_struct("Builder")
280 .field("chunk_capacity", &self.chunk_capacity)
281 .field("initial_chunks", &self.initial_chunks)
282 .finish()
283 }
284}
285
286unsafe fn free_raw_impl<const N: usize>(slab_ptr: *const u8, slot_ptr: *mut u8, chunk_idx: usize) {
290 let slab = unsafe { &*(slab_ptr as *const crate::unbounded::Slab<super::AlignedBytes<N>>) };
291 unsafe {
292 slab.free_ptr_in_chunk(slot_ptr.cast(), chunk_idx);
293 }
294}
295
296impl<const N: usize> core::fmt::Debug for Slab<N> {
297 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
298 f.debug_struct("byte::unbounded::Slab")
299 .field("slot_size", &N)
300 .finish()
301 }
302}
303
304#[cfg(test)]
305#[allow(clippy::float_cmp)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn basic_alloc_free() {
311 let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
312 let ptr = slab.alloc(42u64);
313 assert_eq!(*ptr, 42);
314 slab.free(ptr);
315 }
316
317 #[test]
318 fn heterogeneous_types() {
319 let slab: Slab<128> = unsafe { Slab::with_chunk_capacity(256) };
320
321 let p1 = slab.alloc(42u64);
322 let p2 = slab.alloc(String::from("hello"));
323 let p3 = slab.alloc([1.0f64; 8]);
324
325 assert_eq!(*p1, 42);
326 assert_eq!(&*p2, "hello");
327 assert_eq!(p3[0], 1.0);
328
329 slab.free(p3);
330 slab.free(p2);
331 slab.free(p1);
332 }
333
334 #[test]
335 fn grows_automatically() {
336 let slab: Slab<16> = unsafe { Slab::with_chunk_capacity(2) };
337 let mut ptrs = alloc::vec::Vec::new();
338 for i in 0..100u64 {
339 ptrs.push(slab.alloc(i));
340 }
341 for (i, ptr) in ptrs.iter().enumerate() {
342 assert_eq!(**ptr, i as u64);
343 }
344 for ptr in ptrs {
345 slab.free(ptr);
346 }
347 }
348
349 #[test]
350 fn take_returns_value() {
351 let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
352 let ptr = slab.alloc(String::from("taken"));
353 let val = slab.take(ptr);
354 assert_eq!(val, "taken");
355 }
356
357 #[test]
362 fn claim_write_typed() {
363 let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
364 let claim = slab.claim();
365 let slot = claim.write(42u64);
366 assert_eq!(*slot, 42);
367 slab.free(slot);
368 }
369
370 #[test]
371 fn claim_drop_returns_to_freelist() {
372 let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(1) };
373
374 let claim = slab.claim();
376 drop(claim);
377
378 let claim = slab.claim();
380 let slot = claim.write(99u64);
381 assert_eq!(*slot, 99);
382 slab.free(slot);
383 }
384
385 #[test]
386 fn claim_write_raw() {
387 let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
388 let claim = slab.claim();
389 let val: u64 = 77;
390 let ptr = unsafe {
391 claim.write_raw(&val as *const u64 as *const u8, core::mem::size_of::<u64>())
392 };
393 assert_eq!(unsafe { *(ptr as *const u64) }, 77);
394 let slot = unsafe { super::Slot::<u64>::from_raw(ptr) };
395 slab.free(slot);
396 }
397
398 #[test]
403 fn builder_defaults() {
404 let slab = unsafe { Builder::new().build::<64>() };
405 let slot = slab.alloc(42u64);
406 assert_eq!(*slot, 42);
407 slab.free(slot);
408 }
409
410 #[test]
411 fn builder_custom_chunk_capacity() {
412 let slab = unsafe { Builder::new().chunk_capacity(32).build::<64>() };
413 let slot = slab.alloc(1u64);
414 slab.free(slot);
415 }
416
417 #[test]
418 fn builder_initial_chunks() {
419 let slab = unsafe {
420 Builder::new()
421 .chunk_capacity(16)
422 .initial_chunks(3)
423 .build::<64>()
424 };
425 let mut ptrs = alloc::vec::Vec::new();
427 for i in 0..48u64 {
428 ptrs.push(slab.alloc(i));
429 }
430 for ptr in ptrs {
431 slab.free(ptr);
432 }
433 }
434
435 #[test]
436 #[should_panic(expected = "chunk_capacity must be non-zero")]
437 fn builder_zero_chunk_capacity_panics() {
438 let _slab = unsafe { Builder::new().chunk_capacity(0).build::<64>() };
439 }
440}