vyre_foundation/validate/
options.rs1use crate::dialect_lookup::DialectLookup;
2use crate::ir::DataType;
3
4#[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 pub supports_subgroup_ops: bool,
18 pub supports_indirect_dispatch: bool,
20 pub supports_specialization_constants: bool,
22 pub supports_distributed_collectives: bool,
24 pub has_mul_high: bool,
26 pub has_dual_issue_fp32_int32: bool,
28 pub has_tensor_core_int: bool,
30 pub has_native_f16: bool,
32 pub has_warp_shuffle: bool,
34 pub has_shared_memory: bool,
36 pub has_transcendental_polynomial_emit: bool,
38 pub max_native_int_width: u32,
40}
41
42pub trait BackendValidationCapabilities {
44 fn backend_name(&self) -> &'static str;
46
47 fn supports_cast_target(&self, target: &DataType) -> bool;
50
51 #[inline]
53 fn supports_subgroup_ops(&self) -> bool {
54 false
55 }
56
57 #[inline]
59 fn supports_indirect_dispatch(&self) -> bool {
60 false
61 }
62
63 #[inline]
65 fn supports_specialization_constants(&self) -> bool {
66 false
67 }
68
69 #[inline]
71 fn supports_distributed_collectives(&self) -> bool {
72 false
73 }
74
75 #[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#[derive(Clone, Copy, Default)]
95pub struct ValidationOptions<'a> {
96 pub backend: Option<&'a dyn BackendValidationCapabilities>,
98 pub backend_capabilities: Option<BackendCapabilities>,
100 pub dialect_lookup: Option<&'a dyn DialectLookup>,
102 pub allow_shadowing: bool,
104}
105
106impl<'a> ValidationOptions<'a> {
107 #[must_use]
109 #[inline]
110 pub fn universal() -> Self {
111 Self::default()
112 }
113
114 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}