Skip to main content

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}