Skip to main content

ripsync_core/util/
aligned_buf.rs

1//! Page-aligned buffer allocation for efficient I/O.
2//!
3//! On modern kernels, page-aligned buffers avoid extra copies in the page cache
4//! and can be used with `O_DIRECT` or similar APIs. This module provides a safe
5//! wrapper around platform-specific allocation primitives.
6//!
7//! # Safety
8//!
9//! This is an isolated `unsafe` module (like `io::uring` and `io::windows`).
10//! Every `unsafe` block carries a `// SAFETY:` comment.
11#![allow(unsafe_code)]
12
13use std::alloc::{self, Layout};
14use std::ops::{Deref, DerefMut};
15use std::ptr::NonNull;
16
17/// A page-aligned buffer that deallocates itself on drop.
18///
19/// Implements `Deref<Target = [u8]>` and `DerefMut` so it can be used
20/// wherever a `&[u8]` or `&mut [u8]` is expected.
21pub struct AlignedBuf {
22    ptr: NonNull<u8>,
23    layout: Layout,
24    len: usize,
25}
26
27// SAFETY: AlignedBuf owns a uniquely-allocated buffer; sending across threads
28// is safe because no other reference exists.
29unsafe impl Send for AlignedBuf {}
30
31impl AlignedBuf {
32    /// Allocate a buffer of at least `size` bytes, aligned to the system page size.
33    ///
34    /// The actual allocation is rounded up to the next multiple of the page size.
35    ///
36    /// # Panics
37    ///
38    /// Panics if `size` is zero or if allocation fails.
39    #[must_use]
40    pub fn new(size: usize) -> Self {
41        assert!(size > 0, "buffer size must be non-zero");
42
43        let page_size = page_size();
44        // Round up to page-size multiple so the buffer is always a whole number
45        // of pages (required by some zero-copy APIs).
46        let size = size.next_multiple_of(page_size);
47
48        #[cfg(unix)]
49        let (ptr, layout) = alloc_unix(size, page_size);
50
51        #[cfg(windows)]
52        let (ptr, layout) = alloc_windows(size, page_size);
53
54        #[cfg(not(any(unix, windows)))]
55        let (ptr, layout) = alloc_fallback(size, page_size);
56
57        Self { ptr, layout, len: size }
58    }
59
60    /// Returns the capacity of the buffer in bytes.
61    #[must_use]
62    pub fn len(&self) -> usize {
63        self.len
64    }
65
66    /// Returns whether the buffer is empty (always false for allocated buffers).
67    #[must_use]
68    pub fn is_empty(&self) -> bool {
69        self.len == 0
70    }
71}
72
73impl Deref for AlignedBuf {
74    type Target = [u8];
75
76    fn deref(&self) -> &[u8] {
77        // SAFETY: `ptr` is non-null, properly aligned, and points to `len` bytes
78        // of initialized (though potentially un-written) memory. The borrow lifetime
79        // is tied to `&self`, preventing mutable access while this reference lives.
80        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
81    }
82}
83
84impl DerefMut for AlignedBuf {
85    fn deref_mut(&mut self) -> &mut [u8] {
86        // SAFETY: `ptr` is non-null, properly aligned, and points to `len` bytes
87        // of valid memory. The borrow lifetime is tied to `&mut self`, ensuring
88        // exclusive access.
89        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
90    }
91}
92
93impl Drop for AlignedBuf {
94    fn drop(&mut self) {
95        #[cfg(unix)]
96        {
97            // SAFETY: `ptr` was allocated with `alloc::alloc` using `layout`,
98            // has not been freed, and we hold the only reference.
99            unsafe {
100                alloc::dealloc(self.ptr.as_ptr(), self.layout);
101            }
102        }
103
104        #[cfg(windows)]
105        {
106            // SAFETY: `ptr` was allocated with `_aligned_malloc`, has not been
107            // freed, and we hold the only reference. `_aligned_free` is the
108            // correct deallocator for `_aligned_malloc`.
109            unsafe {
110                _aligned_free(self.ptr.as_ptr().cast::<std::ffi::c_void>());
111            }
112        }
113
114        #[cfg(not(any(unix, windows)))]
115        {
116            // SAFETY: `ptr` was allocated with `alloc::alloc` using `layout`,
117            // has not been freed, and we hold the only reference.
118            unsafe {
119                alloc::dealloc(self.ptr.as_ptr(), self.layout);
120            }
121        }
122    }
123}
124
125/// Get the system page size in bytes.
126#[must_use]
127fn page_size() -> usize {
128    #[cfg(unix)]
129    {
130        rustix::param::page_size()
131    }
132
133    #[cfg(windows)]
134    {
135        use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
136
137        // SAFETY: `GetSystemInfo` is always safe to call; it fills a caller-owned
138        // struct. `SYSTEM_INFO` is `repr(C)` and all bit patterns are valid.
139        let mut sysinfo: SYSTEM_INFO = unsafe { std::mem::zeroed() };
140        unsafe {
141            GetSystemInfo(&mut sysinfo);
142        }
143        sysinfo.dwPageSize as usize
144    }
145
146    #[cfg(not(any(unix, windows)))]
147    {
148        4096 // Common default for unknown platforms
149    }
150}
151
152#[cfg(unix)]
153fn alloc_unix(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
154    let layout =
155        Layout::from_size_align(size, page_size).expect("invalid layout for page-aligned buffer");
156
157    // SAFETY: `layout` has non-zero size (guaranteed by the `size > 0` assert in
158    // `new`). The allocated memory is not read before being written by the caller.
159    let ptr = unsafe { alloc::alloc(layout) };
160    let ptr = NonNull::new(ptr).expect("page-aligned allocation failed");
161
162    (ptr, layout)
163}
164
165#[cfg(windows)]
166fn alloc_windows(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
167    // SAFETY: `_aligned_malloc` is the Windows CRT aligned-allocation function.
168    // `size` and `page_size` are both > 0. The returned pointer is either valid
169    // or null; we check for null below.
170    let ptr = unsafe { _aligned_malloc(size, page_size) };
171    if ptr.is_null() {
172        panic!("page-aligned allocation failed");
173    }
174
175    // SAFETY: `ptr` is non-null (checked above) and points to `size` bytes of
176    // properly-aligned memory from `_aligned_malloc`.
177    let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
178
179    // Dummy layout for API compatibility; the real deallocation uses `_aligned_free`.
180    let layout = Layout::from_size_align(1, 1).unwrap();
181
182    (ptr, layout)
183}
184
185#[cfg(not(any(unix, windows)))]
186fn alloc_fallback(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
187    let layout =
188        Layout::from_size_align(size, page_size).expect("invalid layout for page-aligned buffer");
189
190    // SAFETY: `layout` has non-zero size. The allocated memory is not read before
191    // being written by the caller.
192    let ptr = unsafe { alloc::alloc(layout) };
193    let ptr = NonNull::new(ptr).expect("page-aligned allocation failed");
194
195    (ptr, layout)
196}
197
198#[cfg(windows)]
199unsafe extern "C" {
200    fn _aligned_malloc(size: usize, alignment: usize) -> *mut std::ffi::c_void;
201    fn _aligned_free(ptr: *mut std::ffi::c_void);
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn allocates_page_aligned_buffer() {
210        let buf = AlignedBuf::new(1024);
211        assert!(buf.len() >= 1024);
212        assert!(!buf.is_empty());
213
214        let ptr = buf.ptr.as_ptr() as usize;
215        let ps = page_size();
216        assert_eq!(ptr % ps, 0, "buffer not page-aligned");
217    }
218
219    #[test]
220    fn buffer_is_writable_via_deref_mut() {
221        let mut buf = AlignedBuf::new(1024);
222        buf[0] = 42;
223        buf[999] = 24;
224        assert_eq!(buf[0], 42);
225        assert_eq!(buf[999], 24);
226    }
227
228    #[test]
229    fn deref_returns_full_slice() {
230        let buf = AlignedBuf::new(512);
231        assert_eq!(buf.len(), buf.len());
232        // Verify Deref gives correct length
233        let slice: &[u8] = &buf;
234        assert_eq!(slice.len(), buf.len());
235    }
236
237    #[test]
238    fn respects_minimum_page_size() {
239        let buf = AlignedBuf::new(1);
240        let ps = page_size();
241        assert!(buf.len() >= ps);
242    }
243
244    #[test]
245    fn size_is_page_multiple() {
246        let buf = AlignedBuf::new(100_000);
247        let ps = page_size();
248        assert_eq!(buf.len() % ps, 0);
249    }
250}