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