Skip to main content

monocoque_core/
alloc.rs

1//! Allocation primitives for Monocoque
2//!
3//! This module is the ONLY place where unsafe memory manipulation is allowed.
4//! All invariants are enforced here so the rest of the system can remain 100% safe.
5
6#![allow(unsafe_code)]
7
8use bytes::Bytes;
9use compio::buf::{IoBufMut, SetBufInit};
10use std::alloc::{alloc, dealloc, Layout};
11use std::ptr::NonNull;
12use std::sync::Arc;
13
14/// Size of one slab page.
15/// Tuned for cache locality and amortized allocation cost.
16pub const PAGE_SIZE: usize = 64 * 1024;
17
18/// Cache-line alignment to avoid false sharing.
19pub const PAGE_ALIGN: usize = 128;
20
21/// A slab page: a pinned chunk of memory.
22///
23/// Invariant:
24/// - Memory is allocated once and never moved.
25/// - Freed only when the last Arc<Page> is dropped.
26struct Page {
27    ptr: NonNull<u8>,
28}
29
30unsafe impl Send for Page {}
31unsafe impl Sync for Page {}
32
33impl Drop for Page {
34    fn drop(&mut self) {
35        unsafe {
36            let layout = Layout::from_size_align_unchecked(PAGE_SIZE, PAGE_ALIGN);
37            dealloc(self.ptr.as_ptr(), layout);
38        }
39    }
40}
41
42/// Owner passed into `Bytes::from_owner`
43///
44/// This guarantees:
45/// - The backing slab page stays alive as long as any Bytes exists.
46/// - No aliasing mutable access occurs after freeze.
47struct PageOwner {
48    page: Arc<Page>,
49}
50
51impl AsRef<[u8]> for PageOwner {
52    fn as_ref(&self) -> &[u8] {
53        unsafe { std::slice::from_raw_parts(self.page.ptr.as_ptr(), PAGE_SIZE) }
54    }
55}
56
57/// Mutable slab slice used ONLY during IO.
58///
59/// This type:
60/// - Implements `IoBufMut` so compio can safely DMA into it.
61/// - Is never exposed to user code.
62/// - Is frozen into immutable `Bytes` after IO completes.
63pub struct SlabMut {
64    page: Arc<Page>,
65    ptr: NonNull<u8>,
66    cap: usize,
67    len: usize,
68}
69
70unsafe impl Send for SlabMut {}
71unsafe impl Sync for SlabMut {}
72
73// SAFETY: SlabMut upholds IoBuf invariants:
74// - as_buf_ptr() returns a valid pointer to initialized memory region
75// - The memory region [ptr, ptr + len) contains initialized data
76// - The buffer is pinned and stable during IO operations
77unsafe impl compio::buf::IoBuf for SlabMut {
78    #[inline]
79    fn as_buf_ptr(&self) -> *const u8 {
80        self.ptr.as_ptr()
81    }
82
83    #[inline]
84    fn buf_len(&self) -> usize {
85        self.len
86    }
87
88    #[inline]
89    fn buf_capacity(&self) -> usize {
90        self.cap
91    }
92}
93
94// SAFETY: SlabMut upholds IoBufMut invariants:
95// - as_buf_mut_ptr() returns a valid pointer to a writable memory region
96// - The memory region [ptr, ptr + cap) is exclusively owned
97// - The buffer is pinned and stable during IO operations
98// - set_buf_init correctly updates the initialized length
99unsafe impl IoBufMut for SlabMut {
100    #[inline]
101    fn as_buf_mut_ptr(&mut self) -> *mut u8 {
102        self.ptr.as_ptr()
103    }
104}
105
106impl SetBufInit for SlabMut {
107    #[inline]
108    unsafe fn set_buf_init(&mut self, len: usize) {
109        debug_assert!(len <= self.cap);
110        self.len = len;
111    }
112}
113
114impl SlabMut {
115    /// Freeze this slab into immutable `Bytes`.
116    ///
117    /// # Safety invariants enforced here:
118    /// - `ptr` is inside `page`
119    /// - `len <= cap`
120    /// - After this call, no mutable access exists
121    #[must_use]
122    pub fn freeze(self) -> Bytes {
123        let base = self.page.ptr.as_ptr();
124        let offset = unsafe { self.ptr.as_ptr().offset_from(base) } as usize;
125
126        debug_assert!(offset + self.len <= PAGE_SIZE);
127
128        let owner = PageOwner { page: self.page };
129
130        // Create a Bytes covering the whole page, then slice.
131        let full = Bytes::from_owner(owner);
132        full.slice(offset..offset + self.len)
133    }
134}
135
136/// Arena used by the IO thread.
137///
138/// Not thread-safe by design.
139/// One arena per socket actor.
140pub struct IoArena {
141    current: Option<Arc<Page>>,
142    offset: usize,
143}
144
145impl Default for IoArena {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl IoArena {
152    #[must_use]
153    pub const fn new() -> Self {
154        Self {
155            current: None,
156            offset: PAGE_SIZE, // force alloc on first use
157        }
158    }
159
160    /// Allocate a mutable buffer suitable for a single IO read.
161    ///
162    /// This guarantees:
163    /// - Stable memory address
164    /// - No reallocation
165    /// - No aliasing with other `SlabMut`
166    ///
167    /// # Panics
168    ///
169    /// Panics in debug builds if `size > PAGE_SIZE`.
170    pub fn alloc_mut(&mut self, size: usize) -> SlabMut {
171        debug_assert!(size <= PAGE_SIZE);
172
173        if self.current.is_none() || self.offset + size > PAGE_SIZE {
174            self.alloc_page();
175        }
176
177        let page = self.current.as_ref().unwrap().clone();
178
179        let ptr = unsafe { NonNull::new_unchecked(page.ptr.as_ptr().add(self.offset)) };
180
181        self.offset += size;
182
183        SlabMut {
184            page,
185            ptr,
186            cap: size,
187            len: 0,
188        }
189    }
190
191    #[inline(never)]
192    fn alloc_page(&mut self) {
193        unsafe {
194            let layout = Layout::from_size_align_unchecked(PAGE_SIZE, PAGE_ALIGN);
195            let ptr = alloc(layout);
196            if ptr.is_null() {
197                std::alloc::handle_alloc_error(layout);
198            }
199
200            self.current = Some(Arc::new(Page {
201                ptr: NonNull::new_unchecked(ptr),
202            }));
203            self.offset = 0;
204        }
205    }
206}