vyre_driver/async_copy_overlap.rs
1//! D3 substrate: async-copy / kernel-overlap decision policy.
2//!
3//! When a host→device copy targets a buffer and a downstream kernel
4//! does NOT read that buffer, the copy can run on a separate stream
5//! concurrently with the kernel. This hides copy latency: a 100 µs
6//! H2D transfer overlapped with a 200 µs kernel finishes in 200 µs
7//! total instead of 300 µs serial.
8//!
9//! Pure decision: given the copy's destination slot and the kernel's
10//! `ArmBindingSummary`, can the dispatcher fire the copy on a side
11//! stream and let the kernel run concurrently on the main stream?
12//!
13//! Read-after-copy on the same slot is the unsafe case - kernel
14//! must wait for the copy to land. Otherwise overlap is fine.
15
16use crate::arm_independence::ArmBindingSummary;
17
18/// Verdict for [`can_overlap_copy_with_kernel`].
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum CopyOverlapDecision {
21 /// Copy can run on a side stream concurrently with the kernel -
22 /// the kernel does not read the destination slot.
23 Overlap,
24 /// Kernel reads the slot the copy targets - must serialise (copy
25 /// completes before kernel starts).
26 Serialize,
27}
28
29/// Decide whether a host→device copy targeting `copy_dst_slot` can
30/// overlap with a kernel described by `kernel_arm`. Pure: no IR walk,
31/// no allocation.
32#[must_use]
33pub fn can_overlap_copy_with_kernel(
34 copy_dst_slot: u32,
35 kernel_arm: &ArmBindingSummary,
36) -> CopyOverlapDecision {
37 if kernel_arm.reads.contains(©_dst_slot) {
38 return CopyOverlapDecision::Serialize;
39 }
40 if kernel_arm.writes.contains(©_dst_slot) {
41 // Kernel writes the same slot - RAW would race regardless of
42 // ordering. The runtime should never plan an H2D copy whose
43 // destination is a kernel output, but defensive serialization
44 // keeps the verdict sound.
45 return CopyOverlapDecision::Serialize;
46 }
47 CopyOverlapDecision::Overlap
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 fn arm(reads: &[u32], writes: &[u32]) -> ArmBindingSummary {
55 ArmBindingSummary {
56 reads: reads.iter().copied().collect(),
57 writes: writes.iter().copied().collect(),
58 }
59 }
60
61 #[test]
62 fn copy_to_unread_slot_overlaps() {
63 let kernel = arm(&[0, 1], &[2]);
64 assert_eq!(
65 can_overlap_copy_with_kernel(7, &kernel),
66 CopyOverlapDecision::Overlap
67 );
68 }
69
70 #[test]
71 fn copy_to_kernel_read_slot_serialises() {
72 let kernel = arm(&[0, 1], &[2]);
73 assert_eq!(
74 can_overlap_copy_with_kernel(0, &kernel),
75 CopyOverlapDecision::Serialize
76 );
77 }
78
79 #[test]
80 fn copy_to_kernel_write_slot_serialises() {
81 // Defensive: copying onto kernel's output buffer is suspect,
82 // but if the runtime plans it the substrate must say
83 // Serialize so the kernel sees the copied bytes.
84 let kernel = arm(&[0], &[5]);
85 assert_eq!(
86 can_overlap_copy_with_kernel(5, &kernel),
87 CopyOverlapDecision::Serialize
88 );
89 }
90
91 #[test]
92 fn copy_with_empty_kernel_overlaps() {
93 let kernel = arm(&[], &[]);
94 assert_eq!(
95 can_overlap_copy_with_kernel(0, &kernel),
96 CopyOverlapDecision::Overlap
97 );
98 }
99}