1use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::sync::LazyLock;
6
7use crate::dialect_lookup::Signature;
8use crate::ir::{BufferAccess, Program};
9use crate::program_caps::{scan as scan_capabilities, RequiredCapabilities};
10
11pub type OperationFixtures = fn() -> Vec<Vec<Vec<u8>>>;
13#[derive(Clone, Copy, Debug)]
16pub struct SemanticOperation {
17 pub id: &'static str,
19 pub semantic_version: u32,
21 pub signature: Option<&'static Signature>,
23 pub tier: OperationTier,
25 pub category: Option<&'static str>,
27 pub build: Option<fn() -> Program>,
29 pub test_inputs: Option<OperationFixtures>,
31 pub expected_output: Option<OperationFixtures>,
33 pub laws: &'static [&'static str],
35 pub tolerance: TolerancePolicy,
37}
38
39impl SemanticOperation {
40 #[must_use]
42 pub fn program(self) -> Option<Program> {
43 self.build.map(|build| build().with_entry_op_id(self.id))
44 }
45
46 #[must_use]
48 pub fn required_capabilities(self) -> Option<RequiredCapabilities> {
49 self.program().map(|program| scan_capabilities(&program))
50 }
51
52 #[must_use]
54 pub fn effects(self) -> Option<OperationEffects> {
55 self.program()
56 .map(|program| OperationEffects::from_program(&program))
57 }
58
59 #[must_use]
61 pub const fn category(self) -> Option<&'static str> {
62 self.category
63 }
64
65 #[must_use]
67 pub const fn tolerance(self) -> u32 {
68 self.tolerance.f32_ulp
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74#[non_exhaustive]
75pub enum OperationTier {
76 Foundation,
78 Intrinsic,
80 Primitive,
82 Library,
84 Runtime,
86 External,
88 Unknown,
90}
91
92impl OperationTier {
93 #[must_use]
95 pub const fn matrix_value(self) -> &'static str {
96 match self {
97 Self::Foundation => "foundation_ir",
98 Self::Intrinsic => "intrinsic",
99 Self::Primitive => "primitive",
100 Self::Library => "libs",
101 Self::Runtime => "runtime",
102 Self::External => "external",
103 Self::Unknown => "unknown",
104 }
105 }
106}
107
108#[must_use]
110pub fn classify_operation_id(id: &str) -> OperationTier {
111 if id.starts_with("vyre-intrinsics::hardware::") {
112 OperationTier::Intrinsic
113 } else if id.starts_with("vyre-primitives::") {
114 OperationTier::Primitive
115 } else if id.starts_with("vyre-libs::") {
116 OperationTier::Library
117 } else if id.starts_with("core.") || id.starts_with("io.") || id.starts_with("mem.") {
118 OperationTier::Runtime
119 } else if id
120 .split_once("::")
121 .is_some_and(|(crate_name, _)| !crate_name.is_empty() && !crate_name.starts_with("vyre-"))
122 {
123 OperationTier::External
124 } else {
125 OperationTier::Unknown
126 }
127}
128
129#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
131pub struct OperationEffects {
132 pub reads: bool,
134 pub writes: bool,
136 pub atomics: bool,
138 pub synchronizes: bool,
140}
141
142impl OperationEffects {
143 #[must_use]
145 pub fn from_program(program: &Program) -> Self {
146 let mut effects = Self::default();
147 for buffer in program.buffers() {
148 match buffer.access() {
149 BufferAccess::ReadOnly => effects.reads = true,
150 BufferAccess::ReadWrite => {
151 effects.reads = true;
152 effects.writes = true;
153 }
154 BufferAccess::WriteOnly => effects.writes = true,
155 _ => {
156 effects.reads = true;
157 effects.writes = true;
158 }
159 }
160 }
161 let stats = program.stats();
162 effects.atomics = stats.atomic_op_count > 0;
163 effects.synchronizes = stats.has_node_barrier() || stats.distributed_collectives();
164 effects
165 }
166}
167
168#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
170pub struct TolerancePolicy {
171 pub f32_ulp: u32,
173}
174
175impl TolerancePolicy {
176 pub const EXACT: Self = Self { f32_ulp: 0 };
178
179 #[must_use]
181 pub const fn f32_ulp(maximum: u32) -> Self {
182 Self { f32_ulp: maximum }
183 }
184}
185
186pub struct OperationRegistration {
188 pub id: &'static str,
190 pub semantic_version: u32,
192 pub signature: Option<Signature>,
194 pub tier: OperationTier,
196 pub category: Option<&'static str>,
198 pub build: Option<fn() -> Program>,
200 pub test_inputs: Option<OperationFixtures>,
202 pub expected_output: Option<OperationFixtures>,
204 pub laws: &'static [&'static str],
206 pub tolerance: TolerancePolicy,
208}
209
210impl OperationRegistration {
211 #[must_use]
213 pub const fn new(
214 id: &'static str,
215 tier: OperationTier,
216 build: Option<fn() -> Program>,
217 test_inputs: Option<OperationFixtures>,
218 expected_output: Option<OperationFixtures>,
219 ) -> Self {
220 Self {
221 id,
222 semantic_version: 1,
223 signature: None,
224 tier,
225 category: None,
226 build,
227 test_inputs,
228 expected_output,
229 laws: &[],
230 tolerance: TolerancePolicy::EXACT,
231 }
232 }
233
234 #[must_use]
236 pub const fn library(
237 id: &'static str,
238 build: fn() -> Program,
239 test_inputs: Option<OperationFixtures>,
240 expected_output: Option<OperationFixtures>,
241 ) -> Self {
242 Self::new(
243 id,
244 OperationTier::Library,
245 Some(build),
246 test_inputs,
247 expected_output,
248 )
249 }
250
251 #[must_use]
253 pub const fn primitive(
254 id: &'static str,
255 build: fn() -> Program,
256 test_inputs: Option<OperationFixtures>,
257 expected_output: Option<OperationFixtures>,
258 ) -> Self {
259 Self::new(
260 id,
261 OperationTier::Primitive,
262 Some(build),
263 test_inputs,
264 expected_output,
265 )
266 }
267
268 #[must_use]
270 pub const fn with_signature(mut self, signature: Signature) -> Self {
271 self.signature = Some(signature);
272 self
273 }
274
275 #[must_use]
277 pub const fn with_category(mut self, category: &'static str) -> Self {
278 self.category = Some(category);
279 self
280 }
281
282 #[must_use]
284 pub const fn with_laws(mut self, laws: &'static [&'static str]) -> Self {
285 self.laws = laws;
286 self
287 }
288
289 #[must_use]
291 pub const fn category(&self) -> Option<&'static str> {
292 self.category
293 }
294
295 #[must_use]
297 pub const fn tolerance(&self) -> u32 {
298 self.tolerance.f32_ulp
299 }
300
301 #[must_use]
303 pub const fn with_tolerance(mut self, tolerance: TolerancePolicy) -> Self {
304 self.tolerance = tolerance;
305 self
306 }
307
308 #[must_use]
310 pub fn program(&self) -> Option<Program> {
311 self.build.map(|build| build().with_entry_op_id(self.id))
312 }
313
314 #[must_use]
316 pub fn required_capabilities(&self) -> Option<RequiredCapabilities> {
317 self.program().map(|program| scan_capabilities(&program))
318 }
319
320 #[must_use]
322 pub fn effects(&self) -> Option<OperationEffects> {
323 self.program()
324 .map(|program| OperationEffects::from_program(&program))
325 }
326}
327impl From<&'static OperationRegistration> for SemanticOperation {
328 fn from(registration: &'static OperationRegistration) -> Self {
329 Self {
330 id: registration.id,
331 semantic_version: registration.semantic_version,
332 signature: registration.signature.as_ref(),
333 tier: registration.tier,
334 category: registration.category,
335 build: registration.build,
336 test_inputs: registration.test_inputs,
337 expected_output: registration.expected_output,
338 laws: registration.laws,
339 tolerance: registration.tolerance,
340 }
341 }
342}
343
344inventory::collect!(OperationRegistration);
345
346#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
348pub enum OperationRegistryError {
349 #[error("duplicate operation registration `{id}`; keep exactly one semantic owner")]
351 DuplicateId {
352 id: &'static str,
354 },
355 #[error("operation `{id}` uses semantic version zero; use a positive schema version")]
357 InvalidVersion {
358 id: &'static str,
360 },
361 #[error("operation `{id}` supplies neither a neutral program nor an explicit signature")]
363 MissingSemantics {
364 id: &'static str,
366 },
367 #[error(
369 "operation `{id}` declares tier {declared:?}, but its canonical namespace classifies as {classified:?}"
370 )]
371 InvalidTier {
372 id: &'static str,
374 declared: OperationTier,
376 classified: OperationTier,
378 },
379}
380
381pub struct OperationRegistry {
383 ordered: Vec<&'static OperationRegistration>,
384 by_id: BTreeMap<&'static str, &'static OperationRegistration>,
385}
386
387impl OperationRegistry {
388 fn build() -> Result<Self, OperationRegistryError> {
389 let mut ordered = inventory::iter::<OperationRegistration>
390 .into_iter()
391 .collect::<Vec<_>>();
392 ordered.sort_unstable_by_key(|entry| entry.id);
393 let mut by_id = BTreeMap::new();
394 for entry in &ordered {
395 if entry.semantic_version == 0 {
396 return Err(OperationRegistryError::InvalidVersion { id: entry.id });
397 }
398 if entry.build.is_none() && entry.signature.is_none() {
399 return Err(OperationRegistryError::MissingSemantics { id: entry.id });
400 }
401 let classified = classify_operation_id(entry.id);
402 if classified == OperationTier::Unknown || classified != entry.tier {
403 return Err(OperationRegistryError::InvalidTier {
404 id: entry.id,
405 declared: entry.tier,
406 classified,
407 });
408 }
409 if by_id.insert(entry.id, *entry).is_some() {
410 return Err(OperationRegistryError::DuplicateId { id: entry.id });
411 }
412 }
413 Ok(Self { ordered, by_id })
414 }
415
416 #[must_use]
418 pub fn global() -> &'static Self {
419 static REGISTRY: LazyLock<OperationRegistry> = LazyLock::new(|| {
420 OperationRegistry::build()
421 .unwrap_or_else(|error| panic!("invalid semantic operation registry: {error}"))
422 });
423 ®ISTRY
424 }
425
426 #[must_use]
428 pub fn get(&self, id: &str) -> Option<SemanticOperation> {
429 self.by_id.get(id).copied().map(SemanticOperation::from)
430 }
431
432 pub fn iter(&self) -> impl ExactSizeIterator<Item = SemanticOperation> + '_ {
434 self.ordered.iter().copied().map(SemanticOperation::from)
435 }
436}
437
438#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
443pub struct TargetId(Cow<'static, str>);
444
445impl TargetId {
446 pub const fn new(id: &'static str) -> Result<Self, &'static str> {
452 if id.is_empty() || has_surrounding_ascii_whitespace(id.as_bytes()) {
453 return Err("target identity must be non-empty and contain no surrounding whitespace");
454 }
455 Ok(Self(Cow::Borrowed(id)))
456 }
457
458 pub fn from_owned(id: String) -> Result<Self, &'static str> {
464 if id.is_empty() || has_surrounding_ascii_whitespace(id.as_bytes()) {
465 return Err("target identity must be non-empty and contain no surrounding whitespace");
466 }
467 Ok(Self(Cow::Owned(id)))
468 }
469
470 #[must_use]
472 pub fn as_str(&self) -> &str {
473 self.0.as_ref()
474 }
475
476 #[must_use]
482 pub const fn expect_valid(id: &'static str) -> Self {
483 if id.is_empty() || has_surrounding_ascii_whitespace(id.as_bytes()) {
484 panic!("target identity must be non-empty and contain no surrounding whitespace");
485 }
486 Self(Cow::Borrowed(id))
487 }
488}
489
490impl serde::Serialize for TargetId {
491 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
492 where
493 S: serde::Serializer,
494 {
495 serializer.serialize_str(self.as_str())
496 }
497}
498
499impl<'de> serde::Deserialize<'de> for TargetId {
500 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
501 where
502 D: serde::Deserializer<'de>,
503 {
504 let id = <String as serde::Deserialize>::deserialize(deserializer)?;
505 Self::from_owned(id).map_err(serde::de::Error::custom)
506 }
507}
508
509impl std::fmt::Display for TargetId {
510 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511 formatter.write_str(self.as_str())
512 }
513}
514
515impl PartialEq<&str> for TargetId {
516 fn eq(&self, other: &&str) -> bool {
517 self.as_str() == *other
518 }
519}
520
521const fn has_surrounding_ascii_whitespace(bytes: &[u8]) -> bool {
522 matches!(bytes.first(), Some(byte) if byte.is_ascii_whitespace())
523 || matches!(bytes.last(), Some(byte) if byte.is_ascii_whitespace())
524}
525
526#[derive(Clone, Debug, PartialEq, Eq)]
533pub struct TargetOperationFacet {
534 pub operation_id: &'static str,
536 pub target_id: TargetId,
538 pub version: u32,
540}