cubecl_server/storage/
bytes_cpu.rs1use crate::server::IoError;
2
3use super::{ComputeStorage, StorageHandle, StorageId, StorageUtilization};
4use alloc::alloc::{Layout, alloc_zeroed, dealloc};
5use cubecl_environment::backtrace::BackTrace;
6use cubecl_environment::collections::HashMap;
7
8#[derive(Default)]
10pub struct BytesStorage {
11 memory: HashMap<StorageId, AllocatedBytes>,
12}
13
14impl BytesStorage {
15 pub const ALIGNMENT: usize = 64;
17}
18
19impl core::fmt::Debug for BytesStorage {
20 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21 f.write_str("BytesStorage")
22 }
23}
24
25unsafe impl Send for BytesStorage {}
27unsafe impl Send for BytesResource {}
28
29#[derive(Debug)]
31pub struct BytesResource {
32 ptr: *mut u8,
33 utilization: StorageUtilization,
34}
35
36struct AllocatedBytes {
38 ptr: *mut u8,
39 layout: Layout,
40}
41
42impl BytesResource {
43 pub fn get_write_ptr_and_length(&self) -> (*mut u8, usize) {
45 (
46 unsafe { self.ptr.add(self.utilization.offset as usize) },
49 self.utilization.size as usize,
50 )
51 }
52
53 pub fn write<'a>(&mut self) -> &'a mut [u8] {
60 let (ptr, len) = self.get_write_ptr_and_length();
61
62 unsafe { core::slice::from_raw_parts_mut(ptr, len) }
70 }
71
72 pub fn read<'a>(&self) -> &'a [u8] {
76 let (ptr, len) = self.get_write_ptr_and_length();
77
78 unsafe { core::slice::from_raw_parts(ptr, len) }
83 }
84}
85
86impl ComputeStorage for BytesStorage {
87 type Resource = BytesResource;
88
89 fn alignment(&self) -> usize {
90 Self::ALIGNMENT
91 }
92
93 fn get(&mut self, handle: &StorageHandle) -> Result<Self::Resource, IoError> {
94 let allocated_bytes =
95 self.memory
96 .get(&handle.id)
97 .ok_or_else(|| IoError::StorageHandleNotFound {
98 reason: alloc::format!("{} in the bytes storage", handle.id).into(),
99 backtrace: BackTrace::capture(),
100 })?;
101
102 Ok(BytesResource {
103 ptr: allocated_bytes.ptr,
104 utilization: handle.utilization.clone(),
105 })
106 }
107
108 #[cfg_attr(
109 feature = "tracing",
110 tracing::instrument(level = "trace", skip(self, size))
111 )]
112 fn alloc(&mut self, size: u64) -> Result<StorageHandle, IoError> {
113 let id = StorageId::new();
114 let handle = StorageHandle {
115 id,
116 utilization: StorageUtilization { offset: 0, size },
117 };
118
119 if size == 0 {
120 let memory = AllocatedBytes {
122 ptr: core::ptr::without_provenance_mut(Self::ALIGNMENT),
123 layout: Layout::from_size_align(0, Self::ALIGNMENT).unwrap(),
124 };
125 self.memory.insert(id, memory);
126 } else {
127 unsafe {
128 let layout = Layout::from_size_align(size as usize, Self::ALIGNMENT).unwrap();
129
130 let ptr = alloc_zeroed(layout);
133 if ptr.is_null() {
134 return Err(IoError::BufferTooBig {
135 size,
136 backtrace: BackTrace::capture(),
137 });
138 }
139 let memory = AllocatedBytes { ptr, layout };
140 self.memory.insert(id, memory);
141 }
142 }
143
144 Ok(handle)
145 }
146
147 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
148 fn dealloc(&mut self, id: StorageId) {
149 if let Some(memory) = self.memory.remove(&id)
150 && memory.layout.size() > 0
151 {
152 unsafe {
153 dealloc(memory.ptr, memory.layout);
154 }
155 }
156 }
157
158 fn flush(&mut self) {
159 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test_log::test]
168 fn test_can_alloc_and_dealloc() {
169 let mut storage = BytesStorage::default();
170 let handle_1 = storage.alloc(64).unwrap();
171
172 assert_eq!(handle_1.size(), 64);
173 storage.dealloc(handle_1.id);
174 }
175
176 #[test_log::test]
177 fn test_slices() {
178 let mut storage = BytesStorage::default();
179 let handle_1 = storage.alloc(64).unwrap();
180 let handle_2 = StorageHandle::new(
181 handle_1.id,
182 StorageUtilization {
183 offset: 24,
184 size: 8,
185 },
186 );
187
188 storage
189 .get(&handle_1)
190 .unwrap()
191 .write()
192 .iter_mut()
193 .enumerate()
194 .for_each(|(i, b)| {
195 *b = i as u8;
196 });
197
198 let bytes = storage.get(&handle_2).unwrap().read().to_vec();
199
200 storage.dealloc(handle_1.id);
201 assert_eq!(bytes, &[24, 25, 26, 27, 28, 29, 30, 31]);
202 }
203
204 #[test_log::test]
206 fn test_read_after_alloc_without_write() {
207 let mut storage = BytesStorage::default();
208 let handle = storage.alloc(16).unwrap();
209 let resource = storage.get(&handle).unwrap();
210 assert!(resource.read().iter().all(|&b| b == 0));
211 storage.dealloc(handle.id);
212 }
213
214 #[test_log::test]
216 fn test_zero_size_alloc_and_dealloc() {
217 let mut storage = BytesStorage::default();
218 let handle = storage.alloc(0).unwrap();
219 assert_eq!(handle.size(), 0);
220 storage.dealloc(handle.id);
221 }
222
223 #[test_log::test]
224 fn test_alloc_dealloc_realloc() {
225 let mut storage = BytesStorage::default();
226 let h1 = storage.alloc(32).unwrap();
227 storage.get(&h1).unwrap().write()[0] = 0xAA;
228 storage.dealloc(h1.id);
229 let h2 = storage.alloc(32).unwrap();
230 storage.dealloc(h2.id);
231 }
232
233 #[test_log::test]
234 fn test_multiple_non_overlapping_regions() {
235 let mut storage = BytesStorage::default();
236 let base = storage.alloc(64).unwrap();
237
238 let regions: alloc::vec::Vec<_> = (0..4)
239 .map(|i| {
240 StorageHandle::new(
241 base.id,
242 StorageUtilization {
243 offset: i * 16,
244 size: 16,
245 },
246 )
247 })
248 .collect();
249
250 for (i, region) in regions.iter().enumerate() {
251 storage.get(region).unwrap().write().fill(i as u8);
252 }
253 for (i, region) in regions.iter().enumerate() {
254 assert!(
255 storage
256 .get(region)
257 .unwrap()
258 .read()
259 .iter()
260 .all(|&b| b == i as u8)
261 );
262 }
263 storage.dealloc(base.id);
264 }
265}