Skip to main content

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(&copy_dst_slot) {
38        return CopyOverlapDecision::Serialize;
39    }
40    if kernel_arm.writes.contains(&copy_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}