Skip to main content

dynamo_memory/
system.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! System memory storage backed by malloc.
5
6use super::{MemoryDescriptor, Result, StorageError, StorageKind, actions, nixl::NixlDescriptor};
7use std::any::Any;
8use std::ptr::NonNull;
9
10/// System memory allocated via malloc.
11#[derive(Debug)]
12pub struct SystemStorage {
13    ptr: NonNull<u8>,
14    len: usize,
15}
16
17unsafe impl Send for SystemStorage {}
18unsafe impl Sync for SystemStorage {}
19
20impl SystemStorage {
21    /// Allocate new system memory of the given size.
22    pub fn new(len: usize) -> Result<Self> {
23        if len == 0 {
24            return Err(StorageError::AllocationFailed(
25                "zero-sized allocations are not supported".into(),
26            ));
27        }
28
29        let mut ptr: *mut libc::c_void = std::ptr::null_mut();
30
31        // We need 4KB alignment here for NIXL disk transfers to work.
32        // The O_DIRECT flag is required for GDS.
33        // However, a limitation of this flag is that all operations involving disk
34        // (both read and write) must be page-aligned.
35        // Pinned memory is already page-aligned, so we only need to align system memory.
36        // TODO(jthomson04): Is page size always 4KB?
37
38        // SAFETY: malloc returns suitably aligned memory or null on failure.
39        let result = unsafe { libc::posix_memalign(&mut ptr, 4096, len) };
40        if result != 0 {
41            return Err(StorageError::AllocationFailed(format!(
42                "posix_memalign failed for size {}",
43                len
44            )));
45        }
46        let ptr = NonNull::new(ptr as *mut u8).ok_or_else(|| {
47            StorageError::AllocationFailed(format!("malloc failed for size {}", len))
48        })?;
49
50        // Zero-initialize the memory
51        unsafe {
52            std::ptr::write_bytes(ptr.as_ptr(), 0, len);
53        }
54
55        Ok(Self { ptr, len })
56    }
57
58    /// Get a pointer to the underlying memory.
59    ///
60    /// # Safety
61    /// The caller must ensure the pointer is not used after this storage is dropped.
62    pub unsafe fn as_ptr(&self) -> *const u8 {
63        self.ptr.as_ptr()
64    }
65
66    /// Get a mutable pointer to the underlying memory.
67    ///
68    /// # Safety
69    /// The caller must ensure the pointer is not used after this storage is dropped
70    /// and that there are no other references to this memory.
71    pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 {
72        self.ptr.as_ptr()
73    }
74}
75
76impl Drop for SystemStorage {
77    fn drop(&mut self) {
78        // SAFETY: pointer was allocated by malloc.
79        unsafe {
80            libc::free(self.ptr.as_ptr() as *mut libc::c_void);
81        }
82    }
83}
84
85impl MemoryDescriptor for SystemStorage {
86    fn addr(&self) -> usize {
87        self.ptr.as_ptr() as usize
88    }
89
90    fn size(&self) -> usize {
91        self.len
92    }
93
94    fn storage_kind(&self) -> StorageKind {
95        StorageKind::System
96    }
97
98    fn as_any(&self) -> &dyn Any {
99        self
100    }
101
102    fn nixl_descriptor(&self) -> Option<NixlDescriptor> {
103        None
104    }
105}
106
107// Support for NIXL registration
108impl super::nixl::NixlCompatible for SystemStorage {
109    fn nixl_params(&self) -> (*const u8, usize, nixl_sys::MemType, u64) {
110        (self.ptr.as_ptr(), self.len, nixl_sys::MemType::Dram, 0)
111    }
112}
113
114impl actions::Memset for SystemStorage {
115    fn memset(&mut self, value: u8, offset: usize, size: usize) -> Result<()> {
116        let end = offset
117            .checked_add(size)
118            .ok_or_else(|| StorageError::OperationFailed("memset: offset overflow".into()))?;
119        if end > self.len {
120            return Err(StorageError::OperationFailed(
121                "memset: offset + size > storage size".into(),
122            ));
123        }
124        unsafe {
125            let ptr = self.ptr.as_ptr().add(offset);
126            std::ptr::write_bytes(ptr, value, size);
127        }
128        Ok(())
129    }
130}
131
132impl actions::Slice for SystemStorage {
133    unsafe fn as_slice(&self) -> Result<&[u8]> {
134        // SAFETY: SystemStorage owns the memory allocated via the global allocator.
135        // The memory remains valid as long as this SystemStorage instance exists.
136        // The ptr is guaranteed to be valid for `self.len` bytes.
137        // Caller must ensure no concurrent mutable access per trait contract.
138        // SAFETY: The pointer is valid, properly aligned, and points to `self.len` bytes.
139        Ok(unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) })
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::actions::{Memset, Slice};
147
148    #[test]
149    fn test_system_storage_new() {
150        let storage = SystemStorage::new(1024).expect("allocation should succeed");
151        assert_eq!(storage.size(), 1024);
152        assert!(storage.addr() != 0);
153    }
154
155    #[test]
156    fn test_system_storage_zero_size_fails() {
157        let result = SystemStorage::new(0);
158        assert!(result.is_err());
159    }
160
161    #[test]
162    fn test_system_storage_storage_kind() {
163        let storage = SystemStorage::new(1024).unwrap();
164        assert_eq!(storage.storage_kind(), StorageKind::System);
165    }
166
167    #[test]
168    fn test_system_storage_as_any() {
169        let storage = SystemStorage::new(1024).unwrap();
170        let any = storage.as_any();
171        assert!(any.downcast_ref::<SystemStorage>().is_some());
172    }
173
174    #[test]
175    fn test_system_storage_nixl_descriptor() {
176        let storage = SystemStorage::new(1024).unwrap();
177        // Unregistered storage has no NIXL descriptor
178        assert!(storage.nixl_descriptor().is_none());
179    }
180
181    #[test]
182    fn test_system_storage_as_ptr() {
183        let storage = SystemStorage::new(1024).unwrap();
184        unsafe {
185            let ptr = storage.as_ptr();
186            assert!(!ptr.is_null());
187            assert_eq!(ptr as usize, storage.addr());
188        }
189    }
190
191    #[test]
192    fn test_system_storage_as_mut_ptr() {
193        let mut storage = SystemStorage::new(1024).unwrap();
194        unsafe {
195            let ptr = storage.as_mut_ptr();
196            assert!(!ptr.is_null());
197            assert_eq!(ptr as usize, storage.addr());
198
199            // Write and read back to verify the pointer works
200            *ptr = 0xAB;
201            assert_eq!(*ptr, 0xAB);
202        }
203    }
204
205    #[test]
206    fn test_system_storage_zero_initialized() {
207        let storage = SystemStorage::new(1024).unwrap();
208        unsafe {
209            let slice = storage.as_slice().unwrap();
210            // Memory should be zero-initialized
211            assert!(slice.iter().all(|&b| b == 0));
212        }
213    }
214
215    #[test]
216    fn test_system_storage_memset_and_read() {
217        let mut storage = SystemStorage::new(1024).unwrap();
218        storage.memset(0xCD, 0, 1024).unwrap();
219
220        unsafe {
221            let slice = storage.as_slice().unwrap();
222            assert!(slice.iter().all(|&b| b == 0xCD));
223        }
224    }
225
226    #[test]
227    fn test_system_storage_multiple_allocations_independent() {
228        let storage1 = SystemStorage::new(512).unwrap();
229        let storage2 = SystemStorage::new(512).unwrap();
230
231        // Different allocations should have different addresses
232        assert_ne!(storage1.addr(), storage2.addr());
233    }
234
235    #[test]
236    fn test_system_storage_alignment() {
237        let storage = SystemStorage::new(1024).unwrap();
238        // posix_memalign allocates with 4096-byte alignment
239        assert!(storage.addr().is_multiple_of(4096));
240    }
241
242    #[test]
243    fn test_system_storage_nixl_compatible() {
244        use crate::nixl::NixlCompatible;
245
246        let storage = SystemStorage::new(2048).unwrap();
247        let (ptr, size, mem_type, device_id) = storage.nixl_params();
248
249        assert_eq!(ptr as usize, storage.addr());
250        assert_eq!(size, 2048);
251        assert_eq!(mem_type, nixl_sys::MemType::Dram);
252        assert_eq!(device_id, 0);
253    }
254
255    #[test]
256    fn test_system_storage_large_allocation() {
257        // Allocate 1MB to test larger sizes
258        let storage = SystemStorage::new(1024 * 1024).unwrap();
259        assert_eq!(storage.size(), 1024 * 1024);
260    }
261
262    #[test]
263    fn test_system_storage_debug() {
264        let storage = SystemStorage::new(1024).unwrap();
265        let debug_str = format!("{:?}", storage);
266        assert!(debug_str.contains("SystemStorage"));
267    }
268}