vyre_driver/backend/registry/inventory_streams.rs
1//! Inventory streams contributed by linked backend crates.
2
3use std::collections::HashSet;
4
5use vyre_foundation::ir::OpId;
6
7use super::grid_sync_split::wrap_grid_sync_split;
8use crate::backend::{BackendError, VyreBackend};
9
10/// One backend constructor contributed by a linked backend crate.
11///
12/// Backend construction can fail (missing GPU adapter, unsupported driver),
13/// so the factory returns a [`BackendError`] rather than panicking. Callers
14/// iterate [`registered_backends`] and skip backends whose factory fails on
15/// this host.
16pub struct BackendRegistration {
17 /// Stable backend identifier, matching [`VyreBackend::id`].
18 pub id: &'static str,
19 /// Factory that constructs the backend implementation.
20 ///
21 /// Returns `Err(BackendError)` when the backend cannot initialize on
22 /// this host. The error message must include a `Fix:` remediation section
23 /// per the frozen `BackendError` contract.
24 pub factory: fn() -> Result<Box<dyn VyreBackend>, BackendError>,
25 /// Operation ids supported by this backend.
26 pub supported_ops: fn() -> &'static HashSet<OpId>,
27}
28
29impl BackendRegistration {
30 /// Construct this registered backend through the shared driver boundary.
31 ///
32 /// This preserves the raw factory ABI while ensuring registration-based
33 /// callers receive the same dispatch wrapper as [`crate::backend::acquire`]
34 /// and [`crate::backend::acquire_preferred_dispatch_backend`].
35 ///
36 /// # Errors
37 ///
38 /// Returns the backend factory error when the concrete backend cannot
39 /// initialize on this host.
40 pub fn acquire(&self) -> Result<Box<dyn VyreBackend>, BackendError> {
41 (self.factory)().map(wrap_grid_sync_split)
42 }
43}
44
45inventory::collect!(BackendRegistration);
46
47/// Per-backend precedence rank registered alongside its
48/// [`BackendRegistration`]. Lower rank wins in router selection.
49///
50/// Conventional ranks are backend-owned. A backend that does not submit a
51/// `BackendPrecedence` entry is treated as `u32::MAX`.
52pub struct BackendPrecedence {
53 /// Backend identifier; must match the corresponding
54 /// [`BackendRegistration::id`].
55 pub id: &'static str,
56 /// Sort key. Lower means higher priority.
57 pub rank: u32,
58}
59
60inventory::collect!(BackendPrecedence);
61
62/// Backend capability declaration: whether a backend owns a live dispatch
63/// stack on this host.
64pub struct BackendCapability {
65 /// Backend identifier; must match the corresponding
66 /// [`BackendRegistration::id`].
67 pub id: &'static str,
68 /// `true` when this backend's `dispatch` can execute a Program and return
69 /// real outputs; `false` when the backend is emission-only.
70 pub dispatches: bool,
71}
72
73inventory::collect!(BackendCapability);
74
75/// Return all backend registrations linked into the current binary.
76///
77/// Iteration order is unspecified. Callers that need a specific backend
78/// should look it up by [`BackendRegistration::id`].
79///
80/// # Runtime cost
81///
82/// First call walks the link-time inventory and freezes the result into a
83/// process-wide `OnceLock<Box<[&'static BackendRegistration]>>`. Every
84/// subsequent call is one atomic load and returns the frozen slice with
85/// zero allocation.
86#[must_use]
87pub fn registered_backends() -> &'static [&'static BackendRegistration] {
88 static FROZEN: std::sync::OnceLock<Box<[&'static BackendRegistration]>> =
89 std::sync::OnceLock::new();
90 FROZEN.get_or_init(|| {
91 // HOT-PATH-OK: inventory::iter runs only during OnceLock
92 // initialization; registered_backends returns the frozen slice after
93 // first access.
94 let registration_count = inventory::iter::<BackendRegistration>.into_iter().count();
95 let mut registrations = Vec::new();
96 let _ = registrations.try_reserve_exact(registration_count);
97 // HOT-PATH-OK: this second inventory walk materializes the same
98 // init-only frozen backend slice after capacity has been reserved.
99 registrations.extend(inventory::iter::<BackendRegistration>);
100 registrations.into_boxed_slice()
101 })
102}