Skip to main content

vyre_foundation/validate/
options.rs

1use crate::ir::DataType;
2
3/// Backend-specific validation hooks for capability-sensitive rules.
4///
5/// Foundation validation is backend-agnostic by default. Callers that know the
6/// concrete lowering target can provide a capability implementation here so the
7/// validator rejects IR shapes that would only fail later in a backend.
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9#[expect(
10    clippy::struct_excessive_bools,
11    reason = "backend capability snapshots are explicit feature bits; replacing them with enums would obscure capability checks and break the stable validation ABI"
12)]
13pub struct BackendCapabilities {
14    /// The backend can lower `Expr::SubgroupAdd`, `Expr::SubgroupBallot`, and
15    /// `Expr::SubgroupShuffle`.
16    pub supports_subgroup_ops: bool,
17    /// The backend can lower indirect dispatch paths.
18    pub supports_indirect_dispatch: bool,
19    /// The backend can compile specialization constants.
20    pub supports_specialization_constants: bool,
21    /// The backend can lower distributed collective communication nodes.
22    pub supports_distributed_collectives: bool,
23    /// Backend has native unsigned multiply-high.
24    pub has_mul_high: bool,
25    /// INT32 and FP32 pipelines can execute simultaneously.
26    pub has_dual_issue_fp32_int32: bool,
27    /// Backend supports tensor-core integer matrix multiply.
28    pub has_tensor_core_int: bool,
29    /// Backend supports native f16 arithmetic at useful throughput.
30    pub has_native_f16: bool,
31    /// Backend supports warp-level shuffle primitives.
32    pub has_warp_shuffle: bool,
33    /// Backend supports shared memory with explicit barriers.
34    pub has_shared_memory: bool,
35    /// Backend can emit bounded polynomial approximations for selected transcendentals.
36    pub has_transcendental_polynomial_emit: bool,
37    /// Maximum supported integer width for native operations.
38    pub max_native_int_width: u32,
39}
40
41/// Capability view supplied by a concrete backend during validation.
42pub trait BackendValidationCapabilities {
43    /// Stable backend name used in diagnostics.
44    fn backend_name(&self) -> &'static str;
45
46    /// Return true when the backend can lower a cast whose destination is
47    /// `target`.
48    fn supports_cast_target(&self, target: &DataType) -> bool;
49
50    /// Return true when the backend supports subgroup operations.
51    #[inline]
52    fn supports_subgroup_ops(&self) -> bool {
53        false
54    }
55
56    /// Return true when the backend supports indirect dispatch.
57    #[inline]
58    fn supports_indirect_dispatch(&self) -> bool {
59        false
60    }
61
62    /// Return true when the backend supports specialization constants.
63    #[inline]
64    fn supports_specialization_constants(&self) -> bool {
65        false
66    }
67
68    /// Return true when the backend supports distributed collective nodes.
69    #[inline]
70    fn supports_distributed_collectives(&self) -> bool {
71        false
72    }
73
74    /// Export backend capabilities in a version-stable value object.
75    #[must_use]
76    #[inline]
77    fn backend_capabilities(&self) -> BackendCapabilities {
78        BackendCapabilities {
79            supports_subgroup_ops: self.supports_subgroup_ops(),
80            supports_indirect_dispatch: self.supports_indirect_dispatch(),
81            supports_specialization_constants: self.supports_specialization_constants(),
82            supports_distributed_collectives: self.supports_distributed_collectives(),
83            ..BackendCapabilities::default()
84        }
85    }
86}
87
88/// Configuration for one validation pass.
89///
90/// `ValidationOptions::default()` is a best-effort universal pass: it enforces
91/// backend-independent invariants only. Provide `backend` when the caller knows
92/// the concrete lowering target and wants capability-sensitive rejection.
93#[derive(Clone, Copy, Default)]
94pub struct ValidationOptions<'a> {
95    /// Concrete backend capability surface to validate against.
96    pub backend: Option<&'a dyn BackendValidationCapabilities>,
97    /// Snapshot of backend capabilities for direct feature checks.
98    pub backend_capabilities: Option<BackendCapabilities>,
99    /// Allow nested-scope shadowing explicitly for this validation run.
100    pub allow_shadowing: bool,
101}
102
103impl<'a> ValidationOptions<'a> {
104    /// Build the default best-effort universal validator configuration.
105    #[must_use]
106    #[inline]
107    pub fn universal() -> Self {
108        Self::default()
109    }
110
111    /// Validate against the provided backend capability contract.
112    #[must_use]
113    #[inline]
114    pub fn with_backend(mut self, backend: &'a dyn BackendValidationCapabilities) -> Self {
115        self.backend = Some(backend);
116        self.backend_capabilities = Some(backend.backend_capabilities());
117        self
118    }
119
120    /// Validate against the provided backend capability snapshot.
121    #[must_use]
122    #[inline]
123    pub fn with_backend_capabilities(mut self, backend_capabilities: BackendCapabilities) -> Self {
124        self.backend_capabilities = Some(backend_capabilities);
125        self
126    }
127
128    /// Explicitly allow nested-scope shadowing for this validation pass.
129    #[must_use]
130    #[inline]
131    pub fn with_shadowing(mut self, allow_shadowing: bool) -> Self {
132        self.allow_shadowing = allow_shadowing;
133        self
134    }
135
136    /// Return the backend name carried by this configuration.
137    #[must_use]
138    #[inline]
139    pub fn backend_name(&self) -> &'static str {
140        self.backend.map_or(
141            "best-effort universal",
142            BackendValidationCapabilities::backend_name,
143        )
144    }
145
146    /// Return true when this validation run accepts casts to `target`.
147    #[must_use]
148    #[inline]
149    pub fn supports_cast_target(&self, target: &DataType) -> bool {
150        self.backend
151            .is_none_or(|backend| backend.supports_cast_target(target))
152    }
153
154    /// Return true when this validation run requires subgroup support.
155    #[must_use]
156    #[inline]
157    pub fn requires_subgroup_ops(&self) -> bool {
158        self.backend_capabilities
159            .is_some_and(|caps| caps.supports_subgroup_ops)
160    }
161
162    /// Return true when this validation run accepts distributed collectives.
163    #[must_use]
164    #[inline]
165    pub fn supports_distributed_collectives(&self) -> bool {
166        self.backend_capabilities
167            .is_some_and(|caps| caps.supports_distributed_collectives)
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    struct CapabilityFixtureBackend;
176    impl BackendValidationCapabilities for CapabilityFixtureBackend {
177        fn backend_name(&self) -> &'static str {
178            "capability-fixture-gpu"
179        }
180        fn supports_cast_target(&self, target: &DataType) -> bool {
181            matches!(target, DataType::U32 | DataType::F32)
182        }
183        fn supports_subgroup_ops(&self) -> bool {
184            true
185        }
186    }
187
188    #[test]
189    fn universal_defaults() {
190        let opts = ValidationOptions::universal();
191        assert!(opts.backend.is_none());
192        assert!(!opts.allow_shadowing);
193        assert_eq!(opts.backend_name(), "best-effort universal");
194    }
195
196    #[test]
197    fn with_backend_sets_name_and_caps() {
198        let backend = CapabilityFixtureBackend;
199        let opts = ValidationOptions::universal().with_backend(&backend);
200        assert_eq!(opts.backend_name(), "capability-fixture-gpu");
201        assert!(opts.requires_subgroup_ops());
202    }
203
204    #[test]
205    fn supports_cast_target_delegates_to_backend() {
206        let backend = CapabilityFixtureBackend;
207        let opts = ValidationOptions::universal().with_backend(&backend);
208        assert!(opts.supports_cast_target(&DataType::U32));
209        assert!(!opts.supports_cast_target(&DataType::Bool));
210    }
211
212    #[test]
213    fn supports_cast_target_defaults_true_without_backend() {
214        let opts = ValidationOptions::universal();
215        assert!(opts.supports_cast_target(&DataType::Bool));
216    }
217
218    #[test]
219    fn with_shadowing_toggle() {
220        let opts = ValidationOptions::universal().with_shadowing(true);
221        assert!(opts.allow_shadowing);
222    }
223
224    #[test]
225    fn backend_capabilities_default() {
226        let caps = BackendCapabilities::default();
227        assert!(!caps.supports_subgroup_ops);
228        assert!(!caps.supports_indirect_dispatch);
229        assert!(!caps.supports_specialization_constants);
230        assert!(!caps.supports_distributed_collectives);
231    }
232
233    #[test]
234    fn with_backend_capabilities_snapshot() {
235        let caps = BackendCapabilities {
236            supports_subgroup_ops: true,
237            supports_indirect_dispatch: false,
238            supports_specialization_constants: false,
239            ..BackendCapabilities::default()
240        };
241        let opts = ValidationOptions::universal().with_backend_capabilities(caps);
242        assert!(opts.requires_subgroup_ops());
243    }
244}