1use std::collections::{BTreeMap, HashMap};
9use std::fmt;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, Mutex, OnceLock};
12
13use cudarc::driver::{CudaContext, CudaEvent, CudaStream};
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
20pub struct StreamId(pub u32);
21
22impl StreamId {
23 pub const DEFAULT: StreamId = StreamId(0);
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 struct StorageDropProbe {
35 drops: Arc<std::sync::atomic::AtomicUsize>,
36 released: Arc<std::sync::atomic::AtomicBool>,
37 registry: std::sync::Weak<Mutex<BlockUseRegistry>>,
38 }
39
40 impl MemoryStorageOwner for StorageDropProbe {
41 fn dependencies(&self) -> ResourceResult<Arc<DeviceAccessDependencies>> {
42 panic!("host ownership test must not initialize CUDA dependencies")
43 }
44 }
45
46 impl Drop for StorageDropProbe {
47 fn drop(&mut self) {
48 if let Some(registry) = self.registry.upgrade() {
49 assert!(
50 registry.try_lock().is_ok(),
51 "last storage owner dropped under registry lock"
52 );
53 }
54 self.drops.fetch_add(1, Ordering::SeqCst);
55 self.released.store(true, Ordering::Release);
56 }
57 }
58
59 struct HostOperationOwners {
60 registry: Arc<Mutex<BlockUseRegistry>>,
61 _retained: Vec<RetainedStorageUse>,
62 completion_ready: std::sync::atomic::AtomicBool,
63 }
64
65 impl HostOperationOwners {
66 fn cancel(&self, groups: &[MemoryUseGroup]) -> ResourceResult<()> {
67 assert_eq!(groups.len(), 1);
68 self.registry.lock().unwrap().release_memory_uses(groups[0])
69 }
70 }
71
72 impl crate::launch::RecorderCleanup<MemoryUseGroup> for HostOperationOwners {
73 fn synchronize_retired(&self) -> ResourceResult<()> {
74 if self.completion_ready.load(Ordering::Acquire) {
75 Ok(())
76 } else {
77 Err(ResourceError::Driver(
78 "host operation completion remains unknown".into(),
79 ))
80 }
81 }
82
83 fn cancel_retired(&self, group: MemoryUseGroup) -> ResourceResult<()> {
84 self.cancel(&[group])
85 }
86 }
87
88 fn admitted_owner_operation() -> (
89 Arc<Mutex<BlockUseRegistry>>,
90 Arc<std::sync::atomic::AtomicUsize>,
91 crate::launch::RecorderTransaction<HostOperationOwners, MemoryUseGroup>,
92 ) {
93 let registry = Arc::new(Mutex::new(BlockUseRegistry::default()));
94 let drops = Arc::new(std::sync::atomic::AtomicUsize::new(0));
95 let mut external = Vec::new();
96 for range in [
99 span(0x1000, 64, Access::ReadWrite),
100 span(0x1020, 16, Access::ReadWrite),
101 ] {
102 let released = Arc::new(std::sync::atomic::AtomicBool::new(false));
103 let storage: Arc<dyn MemoryStorageOwner> = Arc::new(StorageDropProbe {
104 drops: Arc::clone(&drops),
105 released: Arc::clone(&released),
106 registry: Arc::downgrade(®istry),
107 });
108 registry
109 .lock()
110 .unwrap()
111 .register_storage(7, range, Arc::downgrade(&storage), released);
112 external.push(storage);
113 }
114 let uses = [span(0x1020, 8, Access::Write)];
115 let mut retained = Vec::new();
116 let group = {
117 let mut registry = registry.lock().unwrap();
118 registry
119 .retain_storage_uses(7, &uses, &mut retained)
120 .unwrap();
121 registry.reserve_memory_uses(7, &uses).unwrap()
122 };
123 let operation = crate::launch::RecorderTransaction::from_admitted(
124 Arc::new(HostOperationOwners {
125 registry: Arc::clone(®istry),
126 _retained: retained,
127 completion_ready: std::sync::atomic::AtomicBool::new(false),
128 }),
129 Box::new([group]),
130 );
131 drop(external);
132 assert_eq!(drops.load(Ordering::SeqCst), 0);
133 (registry, drops, operation)
134 }
135
136 #[test]
137 fn completed_operation_releases_all_alias_owners_once_outside_registry() {
138 let (registry, drops, mut operation) = admitted_owner_operation();
139 operation
140 .enqueue_operation_with(
141 || Ok(()),
142 |_| Ok(()),
143 |_| Ok::<(), ResourceError>(()),
144 |_| Ok(()),
145 HostOperationOwners::cancel,
146 )
147 .unwrap();
148 operation
149 .commit_with(
150 |owner, group| owner.cancel(&[group]),
151 |_| Ok(()),
152 HostOperationOwners::cancel,
153 )
154 .unwrap();
155 assert_eq!(drops.load(Ordering::SeqCst), 2);
156 drop(operation);
157 assert_eq!(drops.load(Ordering::SeqCst), 2);
158 registry
159 .lock()
160 .unwrap()
161 .reserve_memory_uses(7, &[span(0x1020, 8, Access::ReadWrite)])
162 .unwrap();
163 }
164
165 #[test]
166 fn unknown_operation_retains_alias_owners_and_blocks_later_conflicting_access() {
167 let _serial = crate::cuda_graph::capture_lifecycle_test_guard();
168 let (registry, drops, mut operation) = admitted_owner_operation();
169 let owner = Arc::downgrade(operation.prepared_owner().unwrap());
170 let result = operation.enqueue_operation_with(
171 || Ok(()),
172 |_| Ok(()),
173 |_| Err::<(), _>(ResourceError::Driver("partial enqueue".into())),
174 |_| Err(ResourceError::Driver("completion unknown".into())),
175 HostOperationOwners::cancel,
176 );
177 assert!(result.is_err());
178 drop(operation);
179 assert_eq!(drops.load(Ordering::SeqCst), 0);
180 assert!(registry
181 .lock()
182 .unwrap()
183 .reserve_memory_uses(7, &[span(0x1020, 8, Access::Read)])
184 .is_err());
185 crate::cuda_graph::reap_capture_retirements();
188 assert_eq!(drops.load(Ordering::SeqCst), 0);
189 owner
190 .upgrade()
191 .unwrap()
192 .completion_ready
193 .store(true, Ordering::Release);
194 std::thread::spawn(crate::cuda_graph::reap_capture_retirements)
195 .join()
196 .unwrap();
197 assert!(owner.upgrade().is_none());
198 assert_eq!(drops.load(Ordering::SeqCst), 2);
199 let group = registry
200 .lock()
201 .unwrap()
202 .reserve_memory_uses(7, &[span(0x1020, 8, Access::Read)])
203 .unwrap();
204 registry.lock().unwrap().release_memory_uses(group).unwrap();
205 }
206
207 #[test]
208 fn unknown_dependency_preparation_retains_owners_before_the_launch_callback() {
209 let _serial = crate::cuda_graph::capture_lifecycle_test_guard();
210 let (registry, drops, mut operation) = admitted_owner_operation();
211 let owner = Arc::downgrade(operation.prepared_owner().unwrap());
212 let result = operation.enqueue_operation_with(
213 || Ok(()),
214 |_| Err(ResourceError::Driver("dependency wait failed".into())),
215 |_| -> ResourceResult<()> { panic!("launch must not run after failed dependencies") },
216 |_| Err(ResourceError::Driver("completion unknown".into())),
217 HostOperationOwners::cancel,
218 );
219 assert!(matches!(
220 result,
221 Err(crate::launch::LaunchEnqueueError::PreparationAndCleanup { .. })
222 ));
223 drop(operation);
224 assert_eq!(drops.load(Ordering::SeqCst), 0);
225 assert!(registry
226 .lock()
227 .unwrap()
228 .reserve_memory_uses(7, &[span(0x1020, 8, Access::Write)])
229 .is_err());
230 owner
231 .upgrade()
232 .unwrap()
233 .completion_ready
234 .store(true, Ordering::Release);
235 crate::cuda_graph::reap_capture_retirements();
236 assert!(owner.upgrade().is_none());
237 assert_eq!(drops.load(Ordering::SeqCst), 2);
238 }
239
240 #[test]
241 fn allocation_dependency_lookup_validates_the_full_allocation_identity() {
242 let live = read(0x1000, 3).block;
243 live.validate_allocation(64, live, 64).unwrap();
244 assert!(live.validate_allocation(63, live, 64).is_err());
245 assert!(live.validate_allocation(65, live, 64).is_err());
246 for altered in [
247 BlockId {
248 ptr: 0x2000,
249 ..live
250 },
251 BlockId {
252 generation: Generation(2),
253 ..live
254 },
255 BlockId {
256 alloc_stream: StreamId(4),
257 ..live
258 },
259 BlockId {
260 device_ordinal: 1,
261 ..live
262 },
263 ] {
264 assert!(altered.validate_allocation(64, live, 64).is_err());
265 }
266 }
267
268 #[test]
269 fn completed_reclamation_pruning_preserves_unrelated_operation_identity() {
270 let mut registry = BlockUseRegistry::default();
271 let reclamation = crate::memory::AllocationReclamation::default();
272 let block = DeviceBlock {
273 ptr: 0x1000,
274 bytes: 64,
275 align: 8,
276 device_ordinal: 0,
277 alloc_stream: StreamId::DEFAULT,
278 tag: AllocTag::UNTAGGED,
279 generation: Generation(1),
280 state: BlockState::Live,
281 };
282 let free = registry
283 .reserve_reclamation(
284 7,
285 MemoryUse::new(block.ptr, block.bytes, Access::ReadWrite).unwrap(),
286 reclamation.release_proof(),
287 )
288 .unwrap();
289 let ordinary = registry
290 .reserve_memory_uses(7, &[span(0x2000, 64, Access::Write)])
291 .unwrap();
292 reclamation.complete().unwrap();
293 let replacement = registry
294 .reserve_memory_uses(7, &[span(0x1000, 64, Access::Write)])
295 .unwrap();
296 assert!(!registry.prepared_memory.contains_key(&free));
297 assert!(registry.prepared_memory.contains_key(&ordinary));
298 assert!(registry.prepared_memory.contains_key(&replacement));
299 assert_eq!(registry.prepared_memory.len(), 2);
300 registry.release_memory_uses(ordinary).unwrap();
301 assert!(registry.release_memory_uses(ordinary).is_err());
302 assert!(registry
303 .reserve_memory_uses(7, &[span(0x1000, 64, Access::Read)])
304 .is_err());
305 registry.release_memory_uses(replacement).unwrap();
306 }
307
308 fn read(ptr: u64, generation: u64) -> BlockUse {
309 BlockUse {
310 block: BlockId {
311 ptr,
312 generation: Generation(generation),
313 alloc_stream: StreamId(3),
314 device_ordinal: 0,
315 },
316 bytes: 64,
317 access: Access::Read,
318 }
319 }
320
321 #[test]
322 fn pending_alias_blocks_allocation_retirement_until_exact_group_release() {
323 let mut registry = BlockUseRegistry::default();
324 let allocation = span(0x1000, 64, Access::ReadWrite);
325 let first = registry
326 .reserve_memory_uses(7, &[span(0x1010, 8, Access::Read)])
327 .unwrap();
328 let second = registry
329 .reserve_memory_uses(7, &[span(0x1020, 8, Access::Read)])
330 .unwrap();
331 assert!(registry.reserve_memory_uses(7, &[allocation]).is_err());
332 let other_context = registry.reserve_memory_uses(8, &[allocation]).unwrap();
333 registry.release_memory_uses(other_context).unwrap();
334 registry.release_memory_uses(first).unwrap();
335 assert!(registry.release_memory_uses(first).is_err());
336 assert!(registry.reserve_memory_uses(7, &[allocation]).is_err());
337 registry.release_memory_uses(second).unwrap();
338 let released = registry.reserve_memory_uses(7, &[allocation]).unwrap();
339 registry.release_memory_uses(released).unwrap();
340 }
341
342 fn span(start: u64, bytes: usize, access: Access) -> MemoryUse {
343 MemoryUse::new(start, bytes, access).expect("valid test span")
344 }
345
346 #[test]
347 fn memory_access_groups_compare_storage_ranges_not_alias_identity() {
348 let mut registry = BlockUseRegistry::default();
349 let read = span(0x1000, 64, Access::Read);
350 let first = registry.reserve_memory_uses(7, &[read]).unwrap();
351 let alias = span(0x1020, 64, Access::Read);
352 let second = registry.reserve_memory_uses(7, &[alias]).unwrap();
353 assert!(registry
354 .reserve_memory_uses(7, &[span(0x1030, 8, Access::Write)])
355 .is_err());
356 registry.release_memory_uses(first).unwrap();
357 assert!(registry
358 .reserve_memory_uses(7, &[span(0x1040, 8, Access::Write)])
359 .is_err());
360 registry.release_memory_uses(second).unwrap();
361 let writer = registry
362 .reserve_memory_uses(7, &[span(0x1030, 8, Access::Write)])
363 .unwrap();
364 registry.release_memory_uses(writer).unwrap();
365 }
366
367 #[test]
368 fn peer_copy_reserves_both_contexts_atomically() {
369 let mut registry = BlockUseRegistry::default();
370 let source = span(0x1000, 16, Access::Read);
371 let destination = span(0x1000, 16, Access::Write);
372 let occupied = registry.reserve_memory_uses(8, &[destination]).unwrap();
373 assert!(registry
374 .reserve_context_memory_uses(&[(7, source), (8, destination)])
375 .is_err());
376 let source_writer = registry.reserve_memory_uses(7, &[destination]).unwrap();
377 registry.release_memory_uses(source_writer).unwrap();
378 registry.release_memory_uses(occupied).unwrap();
379 let peer = registry
380 .reserve_context_memory_uses(&[(7, source), (8, destination)])
381 .unwrap();
382 assert!(registry.reserve_memory_uses(7, &[destination]).is_err());
383 assert!(registry.reserve_memory_uses(8, &[source]).is_err());
384 let tail = registry
385 .reserve_memory_uses(8, &[span(0x1010, 16, Access::Write)])
386 .unwrap();
387 registry.release_memory_uses(tail).unwrap();
388 registry.release_memory_uses(peer).unwrap();
389 registry.reserve_memory_uses(7, &[destination]).unwrap();
390 registry.reserve_memory_uses(8, &[source]).unwrap();
391 }
392
393 #[test]
394 fn copy_ranges_reject_overlap_only_in_the_same_context() {
395 let source = span(0x1000, 16, Access::Read);
396 let overlapping = span(0x1008, 16, Access::Write);
397 assert!(source.validate_copy_to(7, overlapping, 7).is_err());
398 assert!(source.validate_copy_to(7, source, 7).is_err());
399 source.validate_copy_to(7, overlapping, 8).unwrap();
400 source
401 .validate_copy_to(7, span(0x1010, 16, Access::Write), 7)
402 .unwrap();
403 let empty = span(0x1000, 0, Access::Write);
404 empty.validate_copy_to(7, empty, 7).unwrap();
405 }
406
407 #[test]
408 fn memory_access_groups_preserve_context_and_disjoint_ranges() {
409 let mut registry = BlockUseRegistry::default();
410 let writer = span(0x1000, 64, Access::Write);
411 let first = registry.reserve_memory_uses(7, &[writer]).unwrap();
412 let other_context = registry.reserve_memory_uses(8, &[writer]).unwrap();
413 let adjacent = registry
414 .reserve_memory_uses(7, &[span(0x1040, 8, Access::Write)])
415 .unwrap();
416 let empty = registry
417 .reserve_memory_uses(7, &[span(0x1010, 0, Access::Write)])
418 .unwrap();
419 for group in [first, other_context, adjacent, empty] {
420 registry.release_memory_uses(group).unwrap();
421 }
422 }
423
424 #[test]
425 fn memory_access_group_admission_and_release_are_atomic() {
426 let mut registry = BlockUseRegistry::default();
427 let first = registry
428 .reserve_memory_uses(7, &[span(0x1000, 64, Access::Write)])
429 .unwrap();
430 assert!(registry
431 .reserve_memory_uses(
432 7,
433 &[
434 span(0x2000, 64, Access::Write),
435 span(0x1030, 64, Access::Read),
436 ],
437 )
438 .is_err());
439 let second = registry
441 .reserve_memory_uses(7, &[span(0x2000, 64, Access::Write)])
442 .unwrap();
443 registry.release_memory_uses(second).unwrap();
444 assert!(registry.release_memory_uses(second).is_err());
445 assert!(registry
446 .reserve_memory_uses(7, &[span(0x1000, 64, Access::Read)])
447 .is_err());
448 registry.release_memory_uses(first).unwrap();
449 assert!(MemoryUse::new(u64::MAX, 1, Access::Read).is_err());
450 }
451
452 #[test]
453 fn memory_access_group_preserves_per_range_access_with_internal_aliases() {
454 let mut registry = BlockUseRegistry::default();
455 let group = registry
456 .reserve_memory_uses(
457 7,
458 &[
459 span(0x1000, 64, Access::Read),
460 span(0x1020, 16, Access::Write),
461 span(0x2000, 64, Access::Read),
462 ],
463 )
464 .unwrap();
465 let reader = registry
466 .reserve_memory_uses(7, &[span(0x1000, 16, Access::Read)])
467 .unwrap();
468 assert!(registry
469 .reserve_memory_uses(7, &[span(0x1020, 16, Access::Read)])
470 .is_err());
471 let gap = registry
473 .reserve_memory_uses(7, &[span(0x1800, 64, Access::Write)])
474 .unwrap();
475 for token in [group, reader, gap] {
476 registry.release_memory_uses(token).unwrap();
477 }
478 }
479
480 #[test]
481 fn access_dependencies_keep_published_writes_visible_to_another_stream() {
482 let mut dependencies = AccessDependencies::after_write((7, 1), "allocated");
483 let retired = dependencies.publish((7, 1), "written", Access::Write);
484 drop(retired);
485 let mut waited = Vec::new();
486 dependencies
487 .prepare(&(7, 2), Access::Read, |event| {
488 waited.push(*event);
489 Ok(())
490 })
491 .unwrap();
492 assert_eq!(waited, ["written"]);
493 }
494
495 #[test]
496 fn access_dependencies_preserve_other_streams_until_order_is_proved() {
497 let mut dependencies = AccessDependencies::after_write((7, 1), "written");
498 assert!(dependencies.publish((7, 2), "read", Access::Read).is_none());
499 let mut waited = Vec::new();
500 dependencies
501 .prepare(&(7, 3), Access::Write, |event| {
502 waited.push(*event);
503 Ok(())
504 })
505 .unwrap();
506 assert_eq!(waited, ["written", "read"]);
507 assert!(dependencies
508 .publish((7, 3), "rewritten", Access::Write)
509 .is_none());
510 assert_eq!(dependencies.outstanding_reads, [((7, 2), "read")]);
511 let mut waited = Vec::new();
512 dependencies
513 .prepare(&(7, 4), Access::Read, |event| {
514 waited.push(*event);
515 Ok(())
516 })
517 .unwrap();
518 assert_eq!(waited, ["written", "rewritten"]);
519 }
520
521 #[test]
522 fn access_dependencies_skip_only_the_exact_same_stream() {
523 let dependencies = AccessDependencies::after_write((7, 1), "written");
524 dependencies
525 .prepare(&(7, 1), Access::Read, |_| {
526 panic!("same-stream order needs no wait")
527 })
528 .unwrap();
529 let mut waits = 0;
530 dependencies
531 .prepare(&(8, 1), Access::Read, |_| {
532 waits += 1;
533 Ok(())
534 })
535 .unwrap();
536 assert_eq!(
537 waits, 1,
538 "a pool-local stream index is not a stream identity"
539 );
540 }
541
542 #[test]
543 fn access_dependencies_wait_failure_keeps_all_published_events() {
544 let mut dependencies = AccessDependencies::after_write((7, 1), "written");
545 dependencies.publish((7, 2), "read", Access::Read);
546 assert!(dependencies
547 .prepare(&(7, 3), Access::Write, |_| {
548 Err(ResourceError::Driver("wait failure".into()))
549 })
550 .is_err());
551 assert_eq!(dependencies.outstanding_writes, [((7, 1), "written")]);
552 assert_eq!(dependencies.outstanding_reads, [((7, 2), "read")]);
553 }
554
555 #[test]
556 fn disjoint_operations_do_not_erase_a_concurrently_published_dependency() {
557 let mut registry = BlockUseRegistry::default();
558 let reader = registry
559 .reserve_memory_uses(7, &[span(0x1000, 16, Access::Read)])
560 .unwrap();
561 let writer = registry
562 .reserve_memory_uses(7, &[span(0x1010, 16, Access::Write)])
563 .unwrap();
564 let mut dependencies = AccessDependencies::after_write(0, "allocated");
565 dependencies.prepare(&1, Access::Read, |_| Ok(())).unwrap();
568 dependencies.prepare(&2, Access::Write, |_| Ok(())).unwrap();
569 dependencies.publish(1, "left read", Access::Read);
570 dependencies.publish(2, "right write", Access::Write);
571 registry.release_memory_uses(reader).unwrap();
572 registry.release_memory_uses(writer).unwrap();
573 let mut waited = Vec::new();
574 dependencies
575 .prepare(&3, Access::Write, |event| {
576 waited.push(*event);
577 Ok(())
578 })
579 .unwrap();
580 assert!(waited.contains(&"left read"));
581 assert!(waited.contains(&"right write"));
582 }
583
584 #[test]
585 fn disjoint_writers_keep_both_completion_frontiers() {
586 let mut dependencies = AccessDependencies::after_write(0, "allocated");
587 dependencies.prepare(&1, Access::Write, |_| Ok(())).unwrap();
588 dependencies.prepare(&2, Access::Write, |_| Ok(())).unwrap();
589 dependencies.publish(1, "left write", Access::Write);
590 dependencies.publish(2, "right write", Access::Write);
591 let mut waited = Vec::new();
592 dependencies
593 .prepare(&3, Access::Read, |event| {
594 waited.push(*event);
595 Ok(())
596 })
597 .unwrap();
598 assert!(waited.contains(&"left write"));
599 assert!(waited.contains(&"right write"));
600 }
601}
602
603#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
607pub struct AllocTag(pub &'static str);
608
609impl AllocTag {
610 pub const UNTAGGED: AllocTag = AllocTag("untagged");
611}
612
613pub struct AllocationRequest {
616 pub bytes: usize,
617 pub stream: StreamId,
618 pub tag: AllocTag,
619 pub reservation_pressure_bytes: usize,
621 pub(crate) reclamation: Arc<crate::memory::AllocationReclamation>,
622}
623
624impl AllocationRequest {
625 pub fn new(bytes: usize, stream: StreamId, tag: AllocTag) -> Self {
626 Self {
627 bytes,
628 stream,
629 tag,
630 reservation_pressure_bytes: 0,
631 reclamation: Arc::default(),
632 }
633 }
634
635 pub fn reclamation(&self) -> Arc<crate::memory::AllocationReclamation> {
637 Arc::clone(&self.reclamation)
638 }
639}
640
641#[derive(Debug, Default)]
642struct AllocationAccountingState {
643 outstanding: usize,
644 total_reclaimed: u128,
645}
646
647#[derive(Debug, Default)]
651pub struct AllocationAccounting {
652 state: Mutex<AllocationAccountingState>,
653}
654
655impl AllocationAccounting {
656 pub fn snapshot(&self) -> (usize, u128) {
658 let state = self.state.lock().expect("allocation accounting poisoned");
659 (state.outstanding, state.total_reclaimed)
660 }
661
662 pub(crate) fn acquire(&self, bytes: usize) -> ResourceResult<()> {
665 let mut state = self.state.lock().expect("allocation accounting poisoned");
666 let outstanding = state.outstanding.checked_add(bytes).ok_or_else(|| {
667 ResourceError::Driver("resource allocation accounting overflow".into())
668 })?;
669 state.outstanding = outstanding;
670 Ok(())
671 }
672
673 pub(crate) fn release(&self, bytes: usize) -> ResourceResult<()> {
676 let mut state = self.state.lock().expect("allocation accounting poisoned");
677 let outstanding = state.outstanding.checked_sub(bytes).ok_or_else(|| {
678 ResourceError::Driver("resource release exceeds outstanding bytes".into())
679 })?;
680 let total_reclaimed = state
681 .total_reclaimed
682 .checked_add(bytes as u128)
683 .ok_or_else(|| {
684 ResourceError::Driver("resource reclamation accounting overflow".into())
685 })?;
686 state.outstanding = outstanding;
687 state.total_reclaimed = total_reclaimed;
688 Ok(())
689 }
690}
691
692#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
696pub struct Generation(pub u64);
697
698static GENERATION_COUNTER: AtomicU64 = AtomicU64::new(1);
699
700impl Generation {
701 pub fn next() -> Generation {
704 Generation(GENERATION_COUNTER.fetch_add(1, Ordering::Relaxed))
705 }
706}
707
708#[derive(Clone, Copy, Debug, Eq, PartialEq)]
725pub enum Access {
726 Read,
727 Write,
728 ReadWrite,
729}
730
731impl Access {
732 pub fn reads(self) -> bool {
734 matches!(self, Access::Read | Access::ReadWrite)
735 }
736
737 pub fn writes(self) -> bool {
739 matches!(self, Access::Write | Access::ReadWrite)
740 }
741}
742
743struct AccessDependencies<K, E> {
748 outstanding_writes: Vec<(K, E)>,
749 outstanding_reads: Vec<(K, E)>,
750}
751
752impl<K: PartialEq, E> AccessDependencies<K, E> {
753 #[cfg(test)]
754 fn after_write(stream: K, event: E) -> Self {
755 Self {
756 outstanding_writes: vec![(stream, event)],
757 outstanding_reads: Vec::new(),
758 }
759 }
760
761 fn prepare(
762 &self,
763 stream: &K,
764 access: Access,
765 mut wait: impl FnMut(&E) -> ResourceResult<()>,
766 ) -> ResourceResult<()> {
767 for (writer, event) in &self.outstanding_writes {
768 if writer != stream {
769 wait(event)?;
770 }
771 }
772 if access.writes() {
773 for (reader, event) in &self.outstanding_reads {
774 if reader != stream {
775 wait(event)?;
776 }
777 }
778 }
779 Ok(())
780 }
781
782 fn publish(&mut self, stream: K, event: E, access: Access) -> Option<Self> {
785 let mut retired = Self {
786 outstanding_writes: Vec::new(),
787 outstanding_reads: Vec::new(),
788 };
789 if let Some(index) = self
790 .outstanding_reads
791 .iter()
792 .position(|(key, _)| *key == stream)
793 {
794 retired
795 .outstanding_reads
796 .push(self.outstanding_reads.swap_remove(index));
797 }
798 if access.writes() {
799 if let Some(index) = self
800 .outstanding_writes
801 .iter()
802 .position(|(key, _)| *key == stream)
803 {
804 retired
805 .outstanding_writes
806 .push(self.outstanding_writes.swap_remove(index));
807 }
808 self.outstanding_writes.push((stream, event));
809 } else {
810 self.outstanding_reads.push((stream, event));
811 }
812 if retired.outstanding_reads.is_empty() && retired.outstanding_writes.is_empty() {
813 None
814 } else {
815 Some(retired)
816 }
817 }
818}
819
820struct RetainedAccessEvent {
821 event: CudaEvent,
822 _stream: Arc<CudaStream>,
824}
825
826#[doc(hidden)]
829pub struct DeviceAccessDependencies {
830 _allocation_context: Arc<CudaContext>,
831 state: Mutex<AccessDependencies<u64, Arc<RetainedAccessEvent>>>,
832 pub(crate) reclamation: Arc<crate::memory::AllocationReclamation>,
833 allocation: std::sync::OnceLock<std::sync::Weak<crate::memory::RawDeviceAllocation>>,
834}
835
836impl DeviceAccessDependencies {
837 pub(crate) fn bind_allocation(&self, allocation: &Arc<crate::memory::RawDeviceAllocation>) {
838 assert!(self.allocation.set(Arc::downgrade(allocation)).is_ok());
839 }
840
841 pub(crate) fn retain_allocation(
844 &self,
845 ) -> ResourceResult<Arc<crate::memory::RawDeviceAllocation>> {
846 self.allocation
847 .get()
848 .and_then(std::sync::Weak::upgrade)
849 .ok_or_else(|| {
850 ResourceError::StreamMisuse(
851 "device allocation has entered physical retirement".into(),
852 )
853 })
854 }
855
856 pub(crate) fn mark_allocation_released(&self) -> ResourceResult<()> {
857 self.reclamation.complete()
858 }
859
860 pub(crate) fn allocation_was_released(&self) -> bool {
861 self.reclamation.was_released()
862 }
863 pub(crate) fn synchronize(&self) -> ResourceResult<()> {
866 let fences = {
867 let state = self.state.lock().map_err(|_| {
868 ResourceError::Driver(
869 "cannot prove completion from poisoned device dependency state".into(),
870 )
871 })?;
872 state
873 .outstanding_writes
874 .iter()
875 .chain(&state.outstanding_reads)
876 .map(|(_, fence)| Arc::clone(fence))
877 .collect::<Vec<_>>()
878 };
879 for fence in fences {
880 fence.event.synchronize()?;
881 }
882 Ok(())
883 }
884
885 pub(crate) fn after_ready(
888 context: Arc<CudaContext>,
889 reclamation: Arc<crate::memory::AllocationReclamation>,
890 ) -> Self {
891 Self {
892 reclamation,
893 allocation: std::sync::OnceLock::new(),
894 _allocation_context: context,
895 state: Mutex::new(AccessDependencies {
896 outstanding_writes: Vec::new(),
897 outstanding_reads: Vec::new(),
898 }),
899 }
900 }
901 pub(crate) fn pending_event_count(&self) -> usize {
902 let state = self
903 .state
904 .lock()
905 .expect("device access dependencies poisoned");
906 state.outstanding_reads.len() + state.outstanding_writes.len()
907 }
908
909 pub(crate) fn prepare(&self, stream: &Arc<CudaStream>, access: Access) -> ResourceResult<()> {
910 let execution_id = crate::cuda_graph::stream_execution_id(stream)?;
911 let state = self
912 .state
913 .lock()
914 .expect("device access dependencies poisoned");
915 state.prepare(&execution_id, access, |fence| {
916 stream.wait(&fence.event).map_err(|error| {
917 ResourceError::Driver(format!("device access dependency wait failed: {error}"))
918 })
919 })
920 }
921
922 pub(crate) fn record_completion(
923 &self,
924 stream: Arc<CudaStream>,
925 access: Access,
926 ) -> ResourceResult<()> {
927 let execution_id = crate::cuda_graph::stream_execution_id(&stream)?;
931 let event = stream.context().new_event(None)?;
932 let mut state = self
933 .state
934 .lock()
935 .expect("device access dependencies poisoned");
936 if let Err(error) = event.record(&stream) {
940 drop(state);
941 return Err(ResourceError::Driver(format!(
942 "device access completion event record failed: {error}"
943 )));
944 }
945 let retired = state.publish(
946 execution_id,
947 Arc::new(RetainedAccessEvent {
948 event,
949 _stream: stream,
950 }),
951 access,
952 );
953 drop(state);
954 drop(retired);
955 Ok(())
956 }
957
958 pub(crate) fn record_operation_completion(
961 dependencies: &[(Arc<Self>, Access)],
962 stream: Arc<CudaStream>,
963 ) -> ResourceResult<()> {
964 if dependencies.is_empty() {
965 return Ok(());
966 }
967 let execution_id = crate::cuda_graph::stream_execution_id(&stream)?;
968 let event = stream.context().new_event(None)?;
969 let mut ordered = dependencies.to_vec();
970 ordered.sort_unstable_by_key(|(dependency, _)| Arc::as_ptr(dependency) as usize);
971 let mut combined = Vec::<(Arc<Self>, Access)>::with_capacity(ordered.len());
972 for (dependency, access) in ordered {
973 if let Some((existing, existing_access)) = combined.last_mut() {
974 if Arc::ptr_eq(existing, &dependency) {
975 *existing_access = if existing_access.writes() || access.writes() {
976 Access::ReadWrite
977 } else {
978 Access::Read
979 };
980 continue;
981 }
982 }
983 combined.push((dependency, access));
984 }
985 let mut states = Vec::with_capacity(combined.len());
989 for (dependency, access) in &combined {
990 states.push((
991 dependency
992 .state
993 .lock()
994 .expect("device access dependencies poisoned"),
995 *access,
996 ));
997 }
998 if let Err(error) = event.record(&stream) {
999 drop(states);
1000 return Err(ResourceError::Driver(format!(
1001 "device access completion event record failed: {error}"
1002 )));
1003 }
1004 let event = Arc::new(RetainedAccessEvent {
1007 event,
1008 _stream: stream,
1009 });
1010 let retired = states
1011 .iter_mut()
1012 .filter_map(|(state, access)| state.publish(execution_id, Arc::clone(&event), *access))
1013 .collect::<Vec<_>>();
1014 drop(states);
1015 drop(retired);
1016 Ok(())
1017 }
1018}
1019
1020#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1029pub struct BlockId {
1030 pub ptr: u64,
1031 pub generation: Generation,
1032 pub alloc_stream: StreamId,
1033 pub device_ordinal: u32,
1034}
1035
1036impl BlockId {
1037 pub(crate) fn validate_allocation(
1038 self,
1039 bytes: usize,
1040 actual: Self,
1041 actual_bytes: usize,
1042 ) -> ResourceResult<()> {
1043 if self != actual || bytes != actual_bytes {
1044 return Err(ResourceError::UseAfterFree {
1045 generation: self.generation,
1046 });
1047 }
1048 Ok(())
1049 }
1050
1051 pub fn from_block(block: &DeviceBlock) -> Self {
1056 Self {
1057 ptr: block.ptr,
1058 generation: block.generation,
1059 alloc_stream: block.alloc_stream,
1060 device_ordinal: block.device_ordinal,
1061 }
1062 }
1063}
1064
1065#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1069pub enum BlockState {
1070 Live,
1073 Retired,
1076 Quarantined,
1079 Freed,
1081}
1082
1083#[derive(Debug)]
1092pub struct DeviceBlock {
1093 pub ptr: u64,
1095 pub device_ordinal: u32,
1097 pub alloc_stream: StreamId,
1100 pub bytes: usize,
1103 pub align: usize,
1105 pub tag: AllocTag,
1107 pub generation: Generation,
1109 pub state: BlockState,
1111}
1112
1113#[derive(Debug)]
1117pub enum ResourceError {
1118 AllocationRetained(RetainedAllocation),
1122 OutOfBudget {
1126 requested: usize,
1127 current: usize,
1128 remaining: usize,
1129 limit: usize,
1130 },
1131 Driver(String),
1133 StreamMisuse(String),
1137 UseAfterFree { generation: Generation },
1140 OutOfBounds { generation: Generation },
1143}
1144
1145impl fmt::Display for ResourceError {
1146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1147 match self {
1148 Self::AllocationRetained(owner) => write!(f, "allocation of {} bytes failed after acquisition: {}", owner.bytes, owner.cause),
1149 Self::OutOfBudget {
1150 requested,
1151 current,
1152 remaining,
1153 limit,
1154 } => write!(
1155 f,
1156 "out of budget: current {} bytes, requested {} bytes, required {} bytes, limit {} bytes, remaining {} bytes",
1157 current,
1158 requested,
1159 *current as u128 + *requested as u128,
1160 limit,
1161 remaining,
1162 ),
1163 Self::Driver(msg) => write!(f, "CUDA driver error: {}", msg),
1164 Self::StreamMisuse(msg) => write!(f, "stream-ordered contract violated: {}", msg),
1165 Self::UseAfterFree { generation } => {
1166 write!(f, "use-after-free on generation {:?}", generation)
1167 }
1168 Self::OutOfBounds { generation } => {
1169 write!(f, "out-of-bounds write on generation {:?}", generation)
1170 }
1171 }
1172 }
1173}
1174
1175impl std::error::Error for ResourceError {}
1176
1177pub struct RetainedAllocation {
1180 bytes: usize,
1181 cause: Box<ResourceError>,
1182 reclamation: Arc<crate::memory::AllocationReclamation>,
1183}
1184
1185impl fmt::Debug for RetainedAllocation {
1186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1187 f.debug_struct("RetainedAllocation")
1188 .field("bytes", &self.bytes)
1189 .field("cause", &self.cause)
1190 .field("released", &self.reclamation.was_released())
1191 .finish_non_exhaustive()
1192 }
1193}
1194
1195impl ResourceError {
1196 pub(crate) fn retaining(
1197 self,
1198 bytes: usize,
1199 reclamation: Arc<crate::memory::AllocationReclamation>,
1200 ) -> Self {
1201 Self::AllocationRetained(RetainedAllocation {
1202 bytes,
1203 cause: Box::new(self),
1204 reclamation,
1205 })
1206 }
1207
1208 #[cfg(test)]
1209 pub(crate) fn retained_allocation_bytes(&self) -> usize {
1210 match self {
1211 Self::AllocationRetained(owner) => owner.bytes,
1212 _ => 0,
1213 }
1214 }
1215}
1216
1217pub type ResourceResult<T> = std::result::Result<T, ResourceError>;
1218
1219impl From<cudarc::driver::DriverError> for ResourceError {
1220 fn from(error: cudarc::driver::DriverError) -> Self {
1221 Self::Driver(error.to_string())
1222 }
1223}
1224
1225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1227pub(crate) struct BlockUse {
1228 pub(crate) block: BlockId,
1229 pub(crate) bytes: usize,
1231 pub(crate) access: Access,
1232}
1233
1234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1238pub(crate) struct MemoryUse {
1239 start: u64,
1240 end: u64,
1241 access: Access,
1242}
1243
1244impl MemoryUse {
1245 pub(crate) fn access(self) -> Access {
1246 self.access
1247 }
1248
1249 pub(crate) fn new(start: u64, bytes: usize, access: Access) -> ResourceResult<Self> {
1250 let bytes = u64::try_from(bytes)
1251 .map_err(|_| ResourceError::Driver("device byte range is not representable".into()))?;
1252 let end = start.checked_add(bytes).ok_or_else(|| {
1253 ResourceError::Driver("device byte range overflows its address".into())
1254 })?;
1255 Ok(Self { start, end, access })
1256 }
1257
1258 pub(crate) fn covers(self, other: Self) -> bool {
1259 self.start <= other.start
1260 && self.end >= other.end
1261 && (!other.access.reads() || self.access.reads())
1262 && (!other.access.writes() || self.access.writes())
1263 }
1264
1265 pub(crate) fn validate_copy_to(
1266 self,
1267 source_context: usize,
1268 destination: Self,
1269 destination_context: usize,
1270 ) -> ResourceResult<()> {
1271 if self.end - self.start != destination.end - destination.start {
1272 return Err(ResourceError::StreamMisuse(
1273 "device copy source and destination extents differ".into(),
1274 ));
1275 }
1276 if source_context == destination_context && self.overlaps(destination) {
1277 return Err(ResourceError::StreamMisuse(
1278 "device copy source and destination overlap".into(),
1279 ));
1280 }
1281 Ok(())
1282 }
1283
1284 fn overlaps(self, other: Self) -> bool {
1285 self.start < self.end
1286 && other.start < other.end
1287 && self.start < other.end
1288 && other.start < self.end
1289 }
1290
1291 fn conflicts(self, other: Self) -> bool {
1292 self.overlaps(other) && (self.access.writes() || other.access.writes())
1293 }
1294}
1295
1296pub(crate) trait MemoryStorageOwner: Send + Sync {
1299 fn dependencies(&self) -> ResourceResult<Arc<DeviceAccessDependencies>>;
1300}
1301
1302pub(crate) struct RetainedStorageUse {
1303 pub(crate) owner: Arc<dyn MemoryStorageOwner>,
1304 pub(crate) access: Access,
1305}
1306
1307struct RegisteredStorage {
1308 range: MemoryUse,
1309 owner: std::sync::Weak<dyn MemoryStorageOwner>,
1310 released: Arc<std::sync::atomic::AtomicBool>,
1313}
1314
1315#[derive(Default)]
1316struct RegisteredStorageIndex {
1317 by_start: BTreeMap<u64, Vec<RegisteredStorage>>,
1320 max_span: u64,
1321}
1322
1323#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1326pub(crate) struct MemoryUseGroup(u64);
1327
1328struct PreparedMemoryUses {
1329 uses: Box<[(usize, MemoryUse)]>,
1330 release_proof: Option<Arc<std::sync::atomic::AtomicBool>>,
1331}
1332
1333impl PreparedMemoryUses {
1334 fn physically_released(&self) -> bool {
1335 self.release_proof
1336 .as_ref()
1337 .is_some_and(|proof| proof.load(Ordering::Acquire))
1338 }
1339}
1340
1341#[derive(Default)]
1345pub(crate) struct BlockUseRegistry {
1346 prepared_memory: HashMap<MemoryUseGroup, PreparedMemoryUses>,
1347 next_memory_group: u64,
1348 storage: HashMap<usize, RegisteredStorageIndex>,
1349}
1350
1351pub(crate) fn block_use_registry() -> &'static Mutex<BlockUseRegistry> {
1355 static REGISTRY: OnceLock<Mutex<BlockUseRegistry>> = OnceLock::new();
1356 REGISTRY.get_or_init(|| Mutex::new(BlockUseRegistry::default()))
1357}
1358
1359impl BlockUseRegistry {
1360 pub(crate) fn register_storage(
1361 &mut self,
1362 context: usize,
1363 range: MemoryUse,
1364 owner: std::sync::Weak<dyn MemoryStorageOwner>,
1365 released: Arc<std::sync::atomic::AtomicBool>,
1366 ) {
1367 let index = self.storage.entry(context).or_default();
1368 index.max_span = index.max_span.max(range.end.saturating_sub(range.start));
1369 let entries = index.by_start.entry(range.start).or_default();
1370 entries.retain(|entry| !entry.released.load(std::sync::atomic::Ordering::Acquire));
1371 entries.push(RegisteredStorage {
1372 range,
1373 owner,
1374 released,
1375 });
1376 }
1377
1378 pub(crate) fn retain_storage_uses(
1382 &mut self,
1383 context: usize,
1384 uses: &[MemoryUse],
1385 retained: &mut Vec<RetainedStorageUse>,
1386 ) -> ResourceResult<()> {
1387 let Some(index) = self.storage.get_mut(&context) else {
1388 return Ok(());
1389 };
1390 let mut retained_by_owner = HashMap::with_capacity(retained.len());
1395 for (position, existing) in retained.iter().enumerate() {
1396 retained_by_owner
1397 .entry(Arc::as_ptr(&existing.owner) as *const ())
1398 .or_insert(position);
1399 }
1400 for use_ in uses {
1401 let first_possible_start = use_.start.saturating_sub(index.max_span);
1402 for entries in index
1403 .by_start
1404 .range_mut(first_possible_start..use_.end)
1405 .map(|(_, entries)| entries)
1406 {
1407 entries.retain(|entry| !entry.released.load(std::sync::atomic::Ordering::Acquire));
1408 for entry in entries {
1409 if !use_.overlaps(entry.range) {
1410 continue;
1411 }
1412 let owner = entry.owner.upgrade().ok_or_else(|| {
1413 ResourceError::StreamMisuse(
1414 "overlapping device storage is retiring without proven release".into(),
1415 )
1416 })?;
1417 let owner_key = Arc::as_ptr(&owner) as *const ();
1418 if let Some(&position) = retained_by_owner.get(&owner_key) {
1419 let existing = &mut retained[position];
1420 existing.access = if existing.access.writes() || use_.access.writes() {
1421 Access::ReadWrite
1422 } else {
1423 Access::Read
1424 };
1425 } else {
1426 retained_by_owner.insert(owner_key, retained.len());
1427 retained.push(RetainedStorageUse {
1428 owner,
1429 access: use_.access,
1430 });
1431 }
1432 }
1433 }
1434 }
1435 Ok(())
1436 }
1437
1438 #[cfg(test)]
1443 pub(crate) fn reserve_memory_uses(
1444 &mut self,
1445 context: usize,
1446 uses: &[MemoryUse],
1447 ) -> ResourceResult<MemoryUseGroup> {
1448 self.reserve_context_memory_uses(
1449 &uses.iter().map(|use_| (context, *use_)).collect::<Vec<_>>(),
1450 )
1451 }
1452
1453 pub(crate) fn reserve_context_memory_uses(
1456 &mut self,
1457 uses: &[(usize, MemoryUse)],
1458 ) -> ResourceResult<MemoryUseGroup> {
1459 self.prepared_memory
1460 .retain(|_, pending| !pending.physically_released());
1461 for pending in self.prepared_memory.values() {
1462 if pending.uses.iter().any(|(prior_context, prior)| {
1463 uses.iter().any(|(next_context, next)| {
1464 prior_context == next_context && prior.conflicts(*next)
1465 })
1466 }) {
1467 return Err(ResourceError::StreamMisuse(
1468 "device storage overlaps a conflicting pending operation".into(),
1469 ));
1470 }
1471 }
1472 let next = self.next_memory_group.checked_add(1).ok_or_else(|| {
1473 ResourceError::Driver("device memory-use reservation identity exhausted".into())
1474 })?;
1475 let group = MemoryUseGroup(next);
1476 let uses = uses.to_vec().into_boxed_slice();
1477 self.prepared_memory.insert(
1478 group,
1479 PreparedMemoryUses {
1480 uses,
1481 release_proof: None,
1482 },
1483 );
1484 self.next_memory_group = next;
1485 Ok(group)
1486 }
1487
1488 pub(crate) fn reserve_reclamation(
1491 &mut self,
1492 context: usize,
1493 memory: MemoryUse,
1494 release_proof: Arc<std::sync::atomic::AtomicBool>,
1495 ) -> ResourceResult<MemoryUseGroup> {
1496 let group = self.reserve_context_memory_uses(&[(context, memory)])?;
1497 self.prepared_memory
1498 .get_mut(&group)
1499 .expect("new reservation present")
1500 .release_proof = Some(release_proof);
1501 Ok(group)
1502 }
1503
1504 pub(crate) fn reserve_owned_memory_uses(
1508 &mut self,
1509 uses: &[(usize, MemoryUse)],
1510 source_proofs: &[Arc<std::sync::atomic::AtomicBool>],
1511 ) -> ResourceResult<MemoryUseGroup> {
1512 let group = self.reserve_context_memory_uses(uses)?;
1513 if source_proofs
1514 .iter()
1515 .any(|proof| proof.load(Ordering::Acquire))
1516 {
1517 self.release_memory_uses(group)?;
1518 return Err(ResourceError::StreamMisuse(
1519 "device memory source owner has already been physically released".into(),
1520 ));
1521 }
1522 Ok(group)
1523 }
1524
1525 pub(crate) fn release_memory_uses(&mut self, group: MemoryUseGroup) -> ResourceResult<()> {
1529 self.prepared_memory
1530 .remove(&group)
1531 .map(|_| ())
1532 .ok_or_else(|| {
1533 ResourceError::StreamMisuse("memory-use release has no matching reservation".into())
1534 })
1535 }
1536}
1537
1538#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1540pub struct ResourceBudgetSnapshot {
1541 pub limit: usize,
1542 pub reserved: usize,
1543}
1544
1545impl ResourceBudgetSnapshot {
1546 pub fn remaining(self) -> usize {
1547 self.limit.saturating_sub(self.reserved)
1548 }
1549}
1550
1551pub(crate) fn with_reclamation_admission(
1555 registry: &Mutex<BlockUseRegistry>,
1556 context: usize,
1557 memory: MemoryUse,
1558 released: Arc<std::sync::atomic::AtomicBool>,
1559 admission: &mut Option<MemoryUseGroup>,
1560 reclaim: impl FnOnce() -> ResourceResult<()>,
1561) -> ResourceResult<()> {
1562 if admission.is_none() {
1563 *admission = Some(
1564 registry
1565 .lock()
1566 .expect("device block-use registry poisoned")
1567 .reserve_reclamation(context, memory, released)?,
1568 );
1569 }
1570 reclaim()
1572}
1573
1574pub trait DeviceMemoryResource: Send + Sync {
1591 fn materialize(&self, request: AllocationRequest) -> ResourceResult<DeviceBlock>;
1603
1604 fn allocation_accounting(&self) -> Arc<AllocationAccounting>;
1607
1608 fn allocate(
1611 &self,
1612 bytes: usize,
1613 stream: StreamId,
1614 tag: AllocTag,
1615 ) -> ResourceResult<DeviceBlock> {
1616 self.materialize(AllocationRequest::new(bytes, stream, tag))
1617 }
1618
1619 fn allocate_with_reservation_pressure(
1627 &self,
1628 bytes: usize,
1629 reservation_pressure_bytes: usize,
1630 stream: StreamId,
1631 tag: AllocTag,
1632 ) -> ResourceResult<DeviceBlock> {
1633 let mut request = AllocationRequest::new(bytes, stream, tag);
1634 request.reservation_pressure_bytes = reservation_pressure_bytes;
1635 self.materialize(request)
1636 }
1637
1638 fn deallocate(&self, block: DeviceBlock) -> ResourceResult<()>;
1647
1648 fn device_ordinal(&self) -> u32;
1651
1652 fn bytes_outstanding(&self) -> usize;
1655
1656 fn budget_snapshot(&self) -> Option<ResourceBudgetSnapshot> {
1660 None
1661 }
1662
1663 fn reap_pending(&self) -> ResourceResult<()> {
1674 Ok(())
1675 }
1676
1677 fn record_block_use(&self, block: &DeviceBlock, use_stream: StreamId) -> ResourceResult<()> {
1731 let _ = (block, use_stream);
1732 Err(ResourceError::StreamMisuse(
1733 "record_block_use unsupported by this resource (the active backend \
1734 does not track cross-stream uses; route allocations through a \
1735 stream-ordered backend such as AsyncCudaResource, or take \
1736 responsibility for cross-stream synchronization explicitly)"
1737 .to_string(),
1738 ))
1739 }
1740
1741 fn supports_block_use_tracking(&self) -> bool {
1750 false
1751 }
1752
1753 #[doc(hidden)]
1760 fn access_dependencies(
1761 &self,
1762 _block: BlockId,
1763 _bytes: usize,
1764 ) -> ResourceResult<Option<Arc<DeviceAccessDependencies>>> {
1765 if self.supports_block_use_tracking() {
1766 Err(ResourceError::StreamMisuse(
1767 "tracking allocator does not expose its allocation dependency owner".into(),
1768 ))
1769 } else {
1770 Ok(None)
1771 }
1772 }
1773
1774 fn prepare_block_use(
1802 &self,
1803 block: BlockId,
1804 use_stream: StreamId,
1805 access: Access,
1806 ) -> ResourceResult<()> {
1807 let _ = (block, use_stream, access);
1808 Err(ResourceError::StreamMisuse(
1809 "prepare_block_use unsupported by this resource (the active backend \
1810 does not track cross-stream uses; route allocations through \
1811 AsyncCudaResource or take responsibility for cross-stream \
1812 synchronization explicitly)"
1813 .to_string(),
1814 ))
1815 }
1816
1817 fn finish_block_use(
1833 &self,
1834 block: BlockId,
1835 use_stream: StreamId,
1836 access: Access,
1837 ) -> ResourceResult<()> {
1838 let _ = (block, use_stream, access);
1839 Err(ResourceError::StreamMisuse(
1840 "finish_block_use unsupported by this resource (the active backend \
1841 does not track cross-stream uses; route allocations through \
1842 AsyncCudaResource or take responsibility for cross-stream \
1843 synchronization explicitly)"
1844 .to_string(),
1845 ))
1846 }
1847}