Skip to main content

vyre_foundation/validate/
options.rs

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