Skip to main content

vyre_foundation/execution_plan/fusion/
mod.rs

1#![allow(clippy::unwrap_used)]
2//! Fuse multiple independent Programs into a single combined Program.
3//!
4//! This is the cross-dispatch fusion layer that the megakernel builder and
5//! rule-composition pipeline use to collapse sibling dispatches into one
6//! kernel body.  It is **not** the expression-level fusion pass
7//! (`optimizer::passes::fusion`)  -  that pass lives inside one Program.
8//!
9//! Audit-fix A31 split this module into:
10//!  - `mod.rs`: crate-level attribute + error types + module decls/re-exports
11//!  - `fuse.rs`: `fuse_programs` family + multi-program implementation
12//!  - `collectors.rs`: `collect_*_targets_*` walkers
13//!  - `divergence.rs`: divergence + invocation-gate analysis
14//!  - `helpers.rs`: misc small helpers
15//!  - `tests.rs`: full proptest + unit-test suite
16//!
17//! # Safety invariants
18//!
19//! * Every buffer name that appears in more than one arm is treated as the
20//!   *same* physical GPU buffer. The caller must ensure this is intentional.
21//! * Access-mode upgrades are applied automatically (`ReadOnly` -> `ReadWrite`)
22//!   when any arm needs to write.
23//! * A `Node::Barrier` is inserted between arms when a later arm writes a
24//!   buffer that an earlier arm reads, preventing write-after-read corruption.
25//! * Programs marked `non_composable_with_self` cannot be fused with another
26//!   copy of the same `entry_op_id`.
27
28mod alpha_rename;
29mod collectors;
30mod divergence;
31mod fuse;
32mod helpers;
33
34#[cfg(test)]
35mod tests;
36
37pub use fuse::{fuse_programs, fuse_programs_vec, merge_programs_shared};
38
39/// Error returned when a fusion batch cannot be combined safely.
40#[derive(Debug, Clone, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum FusionError {
43    /// Two copies of a non-composable parser were placed in the same batch.
44    SelfAliasing(FusionSelfAliasingError),
45    /// A cross-arm buffer alias was detected that cannot be fixed by a
46    /// barrier (e.g. both arms write the same buffer without an intervening
47    /// read-only phase).
48    Aliasing(FusionAliasingError),
49    /// The fused launch geometry would over-dispatch the largest arm by
50    /// more than the shared scheduling policy allows. Caller should fall back
51    /// to per-arm dispatch or split the batch.
52    OverDispatch(FusionOverDispatchError),
53    /// An arm whose correctness depends on its own workgroup geometry was
54    /// asked to run under the widened fused geometry.
55    WorkgroupGeometry(FusionWorkgroupGeometryError),
56}
57
58impl std::fmt::Display for FusionError {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            FusionError::SelfAliasing(e) => write!(f, "{e}"),
62            FusionError::Aliasing(e) => write!(f, "{e}"),
63            FusionError::OverDispatch(e) => write!(f, "{e}"),
64            FusionError::WorkgroupGeometry(e) => write!(f, "{e}"),
65        }
66    }
67}
68
69/// An arm that reasons about its own workgroup cannot run under a wider one.
70///
71/// The fused launch takes the axis-wise maximum of the arms' workgroup sizes,
72/// which is harmless for an arm whose invocations are independent. It is not
73/// harmless for an arm that synchronizes its workgroup or keeps state in
74/// workgroup memory: a workgroup barrier has to be reached by every invocation
75/// in the workgroup, and an arm written for 4 invocations guards its body so
76/// that the other 252 skip it. The barrier is then non-uniform, which is
77/// undefined, and the workgroup buffers are sized for the narrow geometry.
78///
79/// The observed symptom was an inclusive prefix scan built for 4 elements,
80/// fused behind a 256-wide elementwise arm, returning the wrong last lane on
81/// roughly one dispatch in ten.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct FusionWorkgroupGeometryError {
84    /// Index of the arm whose geometry would change.
85    pub arm: usize,
86    /// The workgroup size that arm was built for.
87    pub arm_workgroup: [u32; 3],
88    /// The workgroup size the fused program would run it under.
89    pub fused_workgroup: [u32; 3],
90    /// What in the arm makes the widening unsafe.
91    pub reason: &'static str,
92    /// Actionable fix hint.
93    pub fix: &'static str,
94}
95
96impl std::fmt::Display for FusionWorkgroupGeometryError {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        write!(
99            f,
100            "fusion would run arm {} under workgroup {:?} instead of the {:?} it was built for, and that arm {}. Fix: {}",
101            self.arm, self.fused_workgroup, self.arm_workgroup, self.reason, self.fix
102        )
103    }
104}
105
106/// Axis-wise workgroup-max would over-dispatch far above any single arm.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct FusionOverDispatchError {
109    /// Total threads required by the largest single arm.
110    pub max_arm_threads: u64,
111    /// Total threads the fused launch geometry would request.
112    pub fused_threads: u64,
113    /// Actionable fix hint.
114    pub fix: &'static str,
115}
116
117impl std::fmt::Display for FusionOverDispatchError {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        write!(
120            f,
121            "fusion would over-dispatch: fused geometry launches {} threads vs largest single arm {}. Fix: {}",
122            self.fused_threads, self.max_arm_threads, self.fix
123        )
124    }
125}
126
127impl std::error::Error for FusionError {}
128
129/// Two copies of the same parser appeared in one fusion batch.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct FusionSelfAliasingError {
132    /// Operation id shared by both programs.
133    pub op_id: String,
134    /// Actionable fix hint.
135    pub fix: &'static str,
136}
137
138impl std::fmt::Display for FusionSelfAliasingError {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        write!(
141            f,
142            "fusion self-aliasing on op_id `{}`: two copies of a non-composable parser were fused. Fix: {}",
143            self.op_id, self.fix
144        )
145    }
146}
147
148/// Cross-arm buffer access hazard that cannot be repaired automatically.
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct FusionAliasingError {
151    /// Buffer involved in the hazard.
152    pub buffer_name: String,
153    /// Index of the arm that reads the buffer.
154    pub read_arm: usize,
155    /// Index of the arm that writes the buffer.
156    pub write_arm: usize,
157    /// Actionable fix hint.
158    pub fix_hint: &'static str,
159}
160
161impl std::fmt::Display for FusionAliasingError {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(
164            f,
165            "fusion aliasing on buffer `{}`: arm {} reads and arm {} writes without a barrier. Fix: {}",
166            self.buffer_name, self.read_arm, self.write_arm, self.fix_hint
167        )
168    }
169}