1use super::{Access, RuntimeOwner};
2use crate::{Error, Result};
3use std::{
4 ops::{Deref, DerefMut, Range},
5 sync::{Arc, Mutex},
6};
7
8#[derive(Clone)]
11pub enum MemoryPlacement {
12 GpuLocal,
14 HostVisible,
17 #[cfg(feature = "npu")]
19 NpuLocal(crate::npu::NpuProgram),
20 #[cfg(feature = "npu")]
22 Shared(crate::npu::NpuProgram),
23}
24#[derive(Default)]
25pub(super) struct HostState {
26 pub readers: usize,
27 pub writer: bool,
28 pub external: bool,
29 pub poison: Option<String>,
30}
31#[derive(Clone, Copy, PartialEq, Eq)]
32pub(super) enum Engine {
33 Host,
34 Gpu,
35 #[cfg_attr(not(feature = "npu"), allow(dead_code))]
36 Npu,
37}
38pub(super) struct Visibility {
39 pub writer: Engine,
40 pub visible: [bool; 3],
41}
42impl Visibility {
43 pub fn new() -> Self {
44 Self {
45 writer: Engine::Host,
46 visible: [true, false, false],
47 }
48 }
49 #[cfg(any(test, feature = "npu"))]
50 pub fn sync_to_device(&self, consumer: Engine) -> bool {
51 self.writer == Engine::Host || consumer == Engine::Npu
52 }
53 pub fn wrote(&mut self, engine: Engine) {
54 self.writer = engine;
55 self.visible = [false; 3];
56 self.visible[engine as usize] = true;
57 }
58}
59pub(super) struct Storage {
60 pub accounted: bool,
61 #[cfg(feature = "npu")]
63 pub bo: Option<crate::npu::raw::Bo>,
64 #[cfg(feature = "npu")]
65 pub descriptor: Option<std::os::fd::OwnedFd>,
66 pub gpu: Option<crate::gpu::Buffer>,
67 pub pointer: *mut u8,
68 pub bytes: usize,
69 #[cfg(feature = "npu")]
70 pub npu_device: Option<i32>,
71 #[cfg(feature = "npu")]
72 pub group: Option<i32>,
73 pub shared: bool,
74 pub host: Mutex<HostState>,
75 pub visibility: Mutex<Visibility>,
76 pub runtime: Arc<RuntimeOwner>,
77 #[cfg(test)]
78 pub _test_memory: Option<Arc<std::cell::UnsafeCell<[u8; 256]>>>,
79 pub _reservation: Option<crate::residency::MemoryReservation>,
81}
82unsafe impl Send for Storage {}
85unsafe impl Sync for Storage {}
86#[derive(Clone)]
88pub struct Buffer {
89 pub(super) storage: Arc<Storage>,
90}
91#[derive(Clone)]
93pub struct BufferView {
94 pub(super) buffer: Buffer,
95 pub(super) range: Range<usize>,
96 pub(super) retained: Vec<Arc<dyn Send + Sync>>,
97}
98impl Buffer {
99 pub fn len(&self) -> usize {
101 self.storage.bytes
102 }
103 pub fn is_empty(&self) -> bool {
105 self.len() == 0
106 }
107 pub fn view(&self) -> BufferView {
109 BufferView {
110 buffer: self.clone(),
111 range: 0..self.len(),
112 retained: Vec::new(),
113 }
114 }
115 pub fn slice(&self, range: Range<usize>) -> Result<BufferView> {
117 self.view().slice(range)
118 }
119 pub fn map_read(&self) -> Result<ReadGuard<'_>> {
122 self.acquire(false, true)?;
123 Ok(ReadGuard {
124 buffer: self,
125 range: 0..self.len(),
126 })
127 }
128 pub fn try_map_read(&self) -> Result<ReadGuard<'_>> {
130 self.acquire(false, false)?;
131 Ok(ReadGuard {
132 buffer: self,
133 range: 0..self.len(),
134 })
135 }
136 pub fn map_write(&self) -> Result<WriteGuard<'_>> {
138 self.acquire(true, true)?;
139 Ok(WriteGuard {
140 buffer: self,
141 range: 0..self.len(),
142 })
143 }
144 pub fn try_map_write(&self) -> Result<WriteGuard<'_>> {
146 self.acquire(true, false)?;
147 Ok(WriteGuard {
148 buffer: self,
149 range: 0..self.len(),
150 })
151 }
152 fn acquire(&self, write: bool, wait: bool) -> Result<()> {
153 self.acquire_with(write, wait, || self.storage.make_visible(Engine::Host))
154 }
155 pub(super) fn acquire_with(
157 &self,
158 write: bool,
159 wait: bool,
160 make_visible: impl FnOnce() -> Result<()>,
161 ) -> Result<()> {
162 if self.storage.pointer.is_null() {
163 return Err(Error::Unsupported(
164 "GPU-local memory is not host mapped; use an explicit graph copy to shared storage"
165 .into(),
166 ));
167 }
168 let core = &self.storage.runtime.core;
169 let view = self.view();
170 let mut scheduler = core.state.lock().unwrap_or_else(|e| e.into_inner());
171 loop {
172 {
173 let host = self.storage.host.lock().unwrap_or_else(|e| e.into_inner());
174 if let Some(error) = &host.poison {
175 return Err(Error::DeviceLost(error.clone()));
176 }
177 if host.external || host.writer || (write && host.readers != 0) {
178 return Err(Error::Busy("conflicting host mapping".into()));
179 }
180 }
181 if !scheduler.conflicts(
182 &view,
183 if write {
184 Access::ReadWrite
185 } else {
186 Access::Read
187 },
188 ) {
189 break;
190 }
191 if !wait {
192 return Err(Error::Busy("allocation is in device use".into()));
193 }
194 scheduler = core
195 .host_changed
196 .wait(scheduler)
197 .unwrap_or_else(|e| e.into_inner());
198 }
199 let mut host = self.storage.host.lock().unwrap_or_else(|e| e.into_inner());
200 if write {
201 host.writer = true;
202 } else {
203 host.readers += 1;
204 }
205 drop(host);
208 drop(scheduler);
209 if let Err(error) = make_visible() {
210 let mut host = self.storage.host.lock().unwrap_or_else(|e| e.into_inner());
211 if write {
212 host.writer = false;
213 } else {
214 host.readers -= 1;
215 }
216 drop(host);
217 core.host_changed.notify_all();
218 return Err(error);
219 }
220 Ok(())
221 }
222}
223impl BufferView {
224 pub fn is_host_visible(&self) -> bool {
226 !self.buffer.storage.pointer.is_null()
227 }
228 pub fn map_read(&self) -> Result<ReadGuard<'_>> {
231 self.buffer.acquire(false, true)?;
232 Ok(ReadGuard {
233 buffer: &self.buffer,
234 range: self.range.clone(),
235 })
236 }
237 pub fn map_write(&self) -> Result<WriteGuard<'_>> {
240 self.buffer.acquire(true, true)?;
241 Ok(WriteGuard {
242 buffer: &self.buffer,
243 range: self.range.clone(),
244 })
245 }
246 pub fn len(&self) -> usize {
248 self.range.len()
249 }
250 pub fn is_empty(&self) -> bool {
252 self.range.is_empty()
253 }
254 pub fn offset(&self) -> usize {
256 self.range.start
257 }
258 pub fn slice(&self, range: Range<usize>) -> Result<Self> {
260 if range.start > range.end || range.end > self.len() {
261 return Err(Error::Message("buffer view is out of bounds".into()));
262 }
263 Ok(Self {
264 buffer: self.buffer.clone(),
265 range: self.range.start + range.start..self.range.start + range.end,
266 retained: self.retained.clone(),
267 })
268 }
269 pub(crate) fn retain(mut self, owner: Arc<dyn Send + Sync>) -> Self {
270 self.retained.push(owner);
271 self
272 }
273 pub(crate) fn overlaps(&self, other: &Self) -> bool {
274 Arc::ptr_eq(&self.buffer.storage, &other.buffer.storage)
275 && self.range.start < other.range.end
276 && other.range.start < self.range.end
277 }
278 pub(crate) fn same_region(&self, other: &Self) -> bool {
279 Arc::ptr_eq(&self.buffer.storage, &other.buffer.storage) && self.range == other.range
280 }
281 pub(super) fn conflicts(&self, other: &Self) -> bool {
282 Arc::ptr_eq(&self.buffer.storage, &other.buffer.storage)
284 && (self.buffer.storage.shared || self.overlaps(other))
285 }
286 pub(super) fn gpu(&self) -> Result<crate::gpu::View<'_>> {
287 self.buffer
288 .storage
289 .gpu
290 .as_ref()
291 .ok_or_else(|| Error::Unsupported("NPU-local storage cannot bind to the GPU".into()))?
292 .try_slice(self.offset(), self.len())
293 }
294}
295impl Storage {
296 pub fn host_conflict(&self, access: Access) -> Result<()> {
297 let host = self.host.lock().unwrap_or_else(|e| e.into_inner());
298 if let Some(error) = &host.poison {
299 return Err(Error::DeviceLost(error.clone()));
300 }
301 if host.external || host.writer || (access.writes() && host.readers != 0) {
302 return Err(Error::Busy(
303 "device binding conflicts with a live host mapping".into(),
304 ));
305 }
306 Ok(())
307 }
308 pub fn make_visible(&self, engine: Engine) -> Result<()> {
309 let mut visibility = self.visibility.lock().unwrap_or_else(|e| e.into_inner());
310 if visibility.visible[engine as usize] {
311 return Ok(());
312 }
313 #[cfg(feature = "npu")]
314 if let Some(bo) = &self.bo {
315 bo.sync(visibility.sync_to_device(engine), self.bytes)
319 .map_err(Error::Message)?;
320 self.runtime
321 .core
322 .counters
323 .cache_maintenance_bytes
324 .fetch_add(self.bytes as u64, std::sync::atomic::Ordering::Relaxed);
325 }
326 visibility.visible[engine as usize] = true;
327 Ok(())
328 }
329}
330pub struct ReadGuard<'a> {
340 buffer: &'a Buffer,
341 range: Range<usize>,
342}
343impl Deref for ReadGuard<'_> {
344 type Target = [u8];
345 fn deref(&self) -> &[u8] {
346 unsafe {
347 std::slice::from_raw_parts(
348 self.buffer.storage.pointer.add(self.range.start),
349 self.range.len(),
350 )
351 }
352 }
353}
354impl Drop for ReadGuard<'_> {
355 fn drop(&mut self) {
356 self.buffer
357 .storage
358 .host
359 .lock()
360 .unwrap_or_else(|e| e.into_inner())
361 .readers -= 1;
362 self.buffer.storage.runtime.core.host_changed.notify_all();
363 }
364}
365pub struct WriteGuard<'a> {
367 buffer: &'a Buffer,
368 range: Range<usize>,
369}
370impl Deref for WriteGuard<'_> {
371 type Target = [u8];
372 fn deref(&self) -> &[u8] {
373 unsafe {
374 std::slice::from_raw_parts(
375 self.buffer.storage.pointer.add(self.range.start),
376 self.range.len(),
377 )
378 }
379 }
380}
381impl DerefMut for WriteGuard<'_> {
382 fn deref_mut(&mut self) -> &mut [u8] {
383 unsafe {
384 std::slice::from_raw_parts_mut(
385 self.buffer.storage.pointer.add(self.range.start),
386 self.range.len(),
387 )
388 }
389 }
390}
391impl Drop for WriteGuard<'_> {
392 fn drop(&mut self) {
393 self.buffer
395 .storage
396 .visibility
397 .lock()
398 .unwrap_or_else(|e| e.into_inner())
399 .wrote(Engine::Host);
400 self.buffer
401 .storage
402 .host
403 .lock()
404 .unwrap_or_else(|e| e.into_inner())
405 .writer = false;
406 self.buffer.storage.runtime.core.host_changed.notify_all();
407 }
408}
409
410impl Drop for Storage {
411 fn drop(&mut self) {
412 #[cfg(feature = "npu")]
413 if self
414 .visibility
415 .get_mut()
416 .unwrap_or_else(|e| e.into_inner())
417 .writer
418 == Engine::Host
419 && let Some(bo) = &self.bo
420 && bo.sync(true, self.bytes).is_err()
421 {
422 std::mem::forget((
425 self.bo.take(),
426 self.descriptor.take(),
427 self.gpu.take(),
428 self._reservation.take(),
429 ));
430 return;
431 }
432
433 if self.accounted {
434 self.runtime
435 .core
436 .counters
437 .live_bytes
438 .fetch_sub(self.bytes as u64, std::sync::atomic::Ordering::Relaxed);
439 }
440 }
441}