Skip to main content

reddb_server/storage/primitives/
mmap.rs

1// Memory-mapped file I/O for maximum performance
2// ZERO external dependencies - uses raw syscalls on Linux
3// For portability, falls back to regular file I/O on non-Linux
4
5use std::fs::File;
6use std::io;
7use std::ptr;
8
9#[cfg(target_os = "linux")]
10use std::os::unix::io::AsRawFd;
11
12// Linux syscall numbers and constants
13#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
14const SYS_MMAP: i64 = 9;
15#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
16const SYS_MUNMAP: i64 = 11;
17#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
18const SYS_MSYNC: i64 = 26;
19#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
20const SYS_MADVISE: i64 = 28;
21#[cfg(target_os = "linux")]
22const PROT_READ: i32 = 1;
23#[cfg(target_os = "linux")]
24const PROT_WRITE: i32 = 2;
25#[cfg(target_os = "linux")]
26const MAP_SHARED: i32 = 1;
27#[cfg(target_os = "linux")]
28const MAP_FAILED: isize = -1;
29#[cfg(target_os = "linux")]
30const MS_SYNC: i32 = 4;
31#[cfg(target_os = "linux")]
32const MS_ASYNC: i32 = 1;
33#[cfg(target_os = "linux")]
34const MADV_NORMAL: i32 = 0;
35#[cfg(target_os = "linux")]
36const MADV_RANDOM: i32 = 1;
37#[cfg(target_os = "linux")]
38const MADV_SEQUENTIAL: i32 = 2;
39#[cfg(target_os = "linux")]
40const MADV_WILLNEED: i32 = 3;
41#[cfg(target_os = "linux")]
42const MADV_DONTNEED: i32 = 4;
43
44// Macro for raw syscalls (ZERO dependencies!)
45#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
46macro_rules! syscall {
47    ($num:expr, $arg1:expr, $arg2:expr) => {{
48        let mut ret: i64;
49        unsafe {
50            std::arch::asm!(
51                "syscall",
52                inlateout("rax") $num as i64 => ret,
53                in("rdi") $arg1 as i64,
54                in("rsi") $arg2 as i64,
55                lateout("rcx") _,
56                lateout("r11") _,
57                options(nostack)
58            );
59        }
60        ret
61    }};
62    ($num:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{
63        let mut ret: i64;
64        unsafe {
65            std::arch::asm!(
66                "syscall",
67                inlateout("rax") $num as i64 => ret,
68                in("rdi") $arg1 as i64,
69                in("rsi") $arg2 as i64,
70                in("rdx") $arg3 as i64,
71                lateout("rcx") _,
72                lateout("r11") _,
73                options(nostack)
74            );
75        }
76        ret
77    }};
78    ($num:expr, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr, $arg6:expr) => {{
79        let mut ret: i64;
80        unsafe {
81            std::arch::asm!(
82                "syscall",
83                inlateout("rax") $num as i64 => ret,
84                in("rdi") $arg1 as i64,
85                in("rsi") $arg2 as i64,
86                in("rdx") $arg3 as i64,
87                in("r10") $arg4 as i64,
88                in("r8") $arg5 as i64,
89                in("r9") $arg6 as i64,
90                lateout("rcx") _,
91                lateout("r11") _,
92                options(nostack)
93            );
94        }
95        ret
96    }};
97}
98
99#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
100unsafe fn linux_mmap(
101    addr: *mut u8,
102    len: usize,
103    prot: i32,
104    flags: i32,
105    fd: i32,
106    offset: i64,
107) -> isize {
108    syscall!(SYS_MMAP, addr, len, prot, flags, fd, offset) as isize
109}
110
111#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
112unsafe fn linux_mmap(
113    _addr: *mut u8,
114    _len: usize,
115    _prot: i32,
116    _flags: i32,
117    _fd: i32,
118    _offset: i64,
119) -> isize {
120    -1 // mmap not supported on non-x86_64 without libc
121}
122
123#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
124unsafe fn linux_msync(addr: *mut u8, len: usize, flags: i32) -> i64 {
125    syscall!(SYS_MSYNC, addr, len, flags)
126}
127
128#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
129unsafe fn linux_msync(_addr: *mut u8, _len: usize, _flags: i32) -> i64 {
130    -1
131}
132
133#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
134unsafe fn linux_madvise(addr: *mut u8, len: usize, advice: i32) -> i64 {
135    syscall!(SYS_MADVISE, addr, len, advice)
136}
137
138#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
139unsafe fn linux_madvise(_addr: *mut u8, _len: usize, _advice: i32) -> i64 {
140    -1
141}
142
143#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
144unsafe fn linux_munmap(addr: *mut u8, len: usize) -> i64 {
145    syscall!(SYS_MUNMAP, addr, len)
146}
147
148#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
149unsafe fn linux_munmap(_addr: *mut u8, _len: usize) -> i64 {
150    -1
151}
152
153/// Memory access advice for madvise
154#[derive(Debug, Clone, Copy)]
155pub enum MadviseAdvice {
156    Normal,     // No special advice
157    Random,     // Random access pattern
158    Sequential, // Sequential access pattern
159    WillNeed,   // Will need this data soon (prefetch)
160    DontNeed,   // Don't need this data (can drop from cache)
161}
162
163#[cfg(target_os = "linux")]
164pub struct MmapFile {
165    ptr: *mut u8,
166    len: usize,
167    writable: bool,
168    _file: File,
169}
170
171#[cfg(target_os = "linux")]
172impl MmapFile {
173    /// Memory-map a file for reading using raw syscalls
174    pub fn new(file: File, len: usize) -> io::Result<Self> {
175        let fd = file.as_raw_fd();
176
177        // Direct mmap syscall (ZERO dependencies!)
178        let ptr = unsafe { linux_mmap(ptr::null_mut::<u8>(), len, PROT_READ, MAP_SHARED, fd, 0) };
179
180        if ptr == MAP_FAILED {
181            return Err(io::Error::last_os_error());
182        }
183
184        Ok(Self {
185            ptr: ptr as *mut u8,
186            len,
187            writable: false,
188            _file: file,
189        })
190    }
191
192    /// Memory-map a file for read-write using raw syscalls
193    pub fn new_mut(file: File, len: usize) -> io::Result<Self> {
194        let fd = file.as_raw_fd();
195
196        let ptr = unsafe {
197            linux_mmap(
198                ptr::null_mut::<u8>(),
199                len,
200                PROT_READ | PROT_WRITE,
201                MAP_SHARED,
202                fd,
203                0,
204            )
205        };
206
207        if ptr == MAP_FAILED {
208            return Err(io::Error::last_os_error());
209        }
210
211        Ok(Self {
212            ptr: ptr as *mut u8,
213            len,
214            writable: true,
215            _file: file,
216        })
217    }
218
219    /// Get slice view of mapped memory
220    pub fn as_slice(&self) -> &[u8] {
221        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
222    }
223
224    /// Get mutable slice view (only if writable)
225    pub fn as_mut_slice(&mut self) -> io::Result<&mut [u8]> {
226        if !self.writable {
227            return Err(io::Error::new(
228                io::ErrorKind::PermissionDenied,
229                "Mmap is read-only",
230            ));
231        }
232        Ok(unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) })
233    }
234
235    /// Read u32 at offset
236    pub fn read_u32(&self, offset: usize) -> io::Result<u32> {
237        if offset + 4 > self.len {
238            return Err(io::Error::new(
239                io::ErrorKind::UnexpectedEof,
240                "Offset out of bounds",
241            ));
242        }
243
244        let bytes = unsafe { std::slice::from_raw_parts(self.ptr.add(offset), 4) };
245
246        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
247    }
248
249    /// Read bytes at offset
250    pub fn read_bytes(&self, offset: usize, len: usize) -> io::Result<&[u8]> {
251        if offset + len > self.len {
252            return Err(io::Error::new(
253                io::ErrorKind::UnexpectedEof,
254                "Offset out of bounds",
255            ));
256        }
257
258        Ok(unsafe { std::slice::from_raw_parts(self.ptr.add(offset), len) })
259    }
260
261    /// Write bytes at offset
262    pub fn write_bytes(&mut self, offset: usize, data: &[u8]) -> io::Result<()> {
263        if !self.writable {
264            return Err(io::Error::new(
265                io::ErrorKind::PermissionDenied,
266                "Mmap is read-only",
267            ));
268        }
269
270        if offset + data.len() > self.len {
271            return Err(io::Error::new(
272                io::ErrorKind::UnexpectedEof,
273                "Write out of bounds",
274            ));
275        }
276
277        unsafe {
278            ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
279        }
280
281        Ok(())
282    }
283
284    /// Read struct at offset (zero-copy)
285    pub fn read_struct<T: Copy>(&self, offset: usize) -> io::Result<&T> {
286        let size = std::mem::size_of::<T>();
287        if offset + size > self.len {
288            return Err(io::Error::new(
289                io::ErrorKind::UnexpectedEof,
290                "Struct read out of bounds",
291            ));
292        }
293
294        unsafe { Ok(&*(self.ptr.add(offset) as *const T)) }
295    }
296
297    /// Read mutable struct at offset (zero-copy)
298    pub fn read_struct_mut<T: Copy>(&mut self, offset: usize) -> io::Result<&mut T> {
299        if !self.writable {
300            return Err(io::Error::new(
301                io::ErrorKind::PermissionDenied,
302                "Mmap is read-only",
303            ));
304        }
305
306        let size = std::mem::size_of::<T>();
307        if offset + size > self.len {
308            return Err(io::Error::new(
309                io::ErrorKind::UnexpectedEof,
310                "Struct read out of bounds",
311            ));
312        }
313
314        unsafe { Ok(&mut *(self.ptr.add(offset) as *mut T)) }
315    }
316
317    /// Flush changes to disk (sync)
318    pub fn flush(&self) -> io::Result<()> {
319        if !self.writable {
320            return Ok(()); // No-op for read-only
321        }
322
323        let result = unsafe { linux_msync(self.ptr, self.len, MS_SYNC) };
324
325        if result < 0 {
326            return Err(io::Error::last_os_error());
327        }
328
329        Ok(())
330    }
331
332    /// Flush changes to disk (async)
333    pub fn flush_async(&self) -> io::Result<()> {
334        if !self.writable {
335            return Ok(()); // No-op for read-only
336        }
337
338        let result = unsafe { linux_msync(self.ptr, self.len, MS_ASYNC) };
339
340        if result < 0 {
341            return Err(io::Error::last_os_error());
342        }
343
344        Ok(())
345    }
346
347    /// Advise kernel about access pattern
348    pub fn advise(&self, advice: MadviseAdvice) -> io::Result<()> {
349        let advice_flag = match advice {
350            MadviseAdvice::Normal => MADV_NORMAL,
351            MadviseAdvice::Random => MADV_RANDOM,
352            MadviseAdvice::Sequential => MADV_SEQUENTIAL,
353            MadviseAdvice::WillNeed => MADV_WILLNEED,
354            MadviseAdvice::DontNeed => MADV_DONTNEED,
355        };
356
357        let result = unsafe { linux_madvise(self.ptr, self.len, advice_flag) };
358
359        if result < 0 {
360            return Err(io::Error::last_os_error());
361        }
362
363        Ok(())
364    }
365
366    /// Get size of mapped region
367    pub fn len(&self) -> usize {
368        self.len
369    }
370
371    /// Check if empty
372    pub fn is_empty(&self) -> bool {
373        self.len == 0
374    }
375}
376
377#[cfg(target_os = "linux")]
378impl Drop for MmapFile {
379    fn drop(&mut self) {
380        let _ = unsafe { linux_munmap(self.ptr, self.len) };
381    }
382}
383
384// Simpler fallback for non-Linux systems - just disable mmap
385#[cfg(not(target_os = "linux"))]
386pub struct MmapFile {
387    _placeholder: (),
388}
389
390#[cfg(not(target_os = "linux"))]
391impl MmapFile {
392    pub fn new(_file: File, _len: usize) -> io::Result<Self> {
393        Err(io::Error::new(
394            io::ErrorKind::Unsupported,
395            "mmap only supported on Linux (use regular file I/O)",
396        ))
397    }
398
399    pub fn as_slice(&self) -> &[u8] {
400        &[]
401    }
402
403    pub fn read_u32(&self, _offset: usize) -> io::Result<u32> {
404        Err(io::Error::new(
405            io::ErrorKind::Unsupported,
406            "Not implemented",
407        ))
408    }
409
410    pub fn read_bytes(&self, _offset: usize, _len: usize) -> io::Result<&[u8]> {
411        Err(io::Error::new(
412            io::ErrorKind::Unsupported,
413            "Not implemented",
414        ))
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use std::fs::OpenOptions;
422    use std::io::Write;
423
424    #[test]
425    #[cfg(target_os = "linux")]
426    fn test_mmap_basic() {
427        let path = "/tmp/mmap_test.dat";
428
429        // Create test file
430        let mut file = OpenOptions::new()
431            .write(true)
432            .create(true)
433            .truncate(true)
434            .open(path)
435            .unwrap();
436
437        file.write_all(b"Hello, mmap!").unwrap();
438        drop(file);
439
440        // Open for mmap
441        let file = OpenOptions::new().read(true).open(path).unwrap();
442        let len = file.metadata().unwrap().len() as usize;
443
444        let mmap = MmapFile::new(file, len).unwrap();
445        let data = mmap.as_slice();
446
447        assert_eq!(data, b"Hello, mmap!");
448
449        std::fs::remove_file(path).unwrap();
450    }
451}