Skip to main content

bb_runtime/
registry.rs

1//! Global registries collected via `inventory`.
2//!
3//! lives in [`bb_ir::registry`] (foundation, shared by `bb-dsl` and
4//! `bb-runtime`). This module re-exports it and adds the custom-op
5//! registry, which depends on runtime-side types (`RuntimeResourceRef`,
6//! `OpError`, `DispatchResult`) and therefore stays alongside the
7//! engine.
8//!
9//! See the module-level docs in `bb_ir::registry` for the
10//! concrete-component half's authoring patterns. The custom-op
11//! registry follows the same `inventory::submit!` pattern; the
12//! `bb::register_op!{}` declarative macro emits the submission for
13//! library makers.
14
15// Re-export the concrete-component registry so legacy
16// `crate::registry::ConcreteComponentRegistration` paths (emitted by
17// the bb-derive proc macros) keep resolving.
18pub use bb_ir::component::DependencyDecl;
19pub use bb_ir::registry::{
20    concrete_components, find_concrete_component, inventory, ConcreteComponentRegistration,
21};
22
23// --- Unified op registration ---------------------------------------
24//
25// One `OpRegistration` covers every op the framework dispatches —
26// user-shipped via `bb::register_op!` AND framework-shipped from
27// `bb-ops`. `RegistrationKind::{Custom, Syscall}` discriminates
28// which iterator surface returns the entry.
29
30/// Type-erased dispatch fn for any op (custom OR syscall).
31pub type OpInvokeFn = fn(
32    &bb_ir::proto::onnx::NodeProto,
33    &[(&str, &dyn crate::slot_value::SlotValue)],
34    &mut crate::runtime::RuntimeResourceRef<'_>,
35) -> Result<crate::atomic::DispatchResult, crate::bus::OpError>;
36
37/// User-custom vs framework-syscall discriminator on
38/// [`OpRegistration`]. Iterators surface entries filtered by kind.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum RegistrationKind {
41    /// User-shipped custom op (emitted by `bb::register_op!`).
42    Custom,
43    /// Framework-shipped syscall (emitted by every `bb-ops`
44    /// component inventory submission).
45    Syscall,
46}
47
48/// Single inventory entry covering every op the framework
49/// dispatches. Library makers ship via `bb::register_op!`; bb-ops's
50/// syscalls submit directly. Engine dispatch keys on
51/// `(domain, op_type)` — no TypeId lookup.
52pub struct OpRegistration {
53    /// Op's `(domain, op_type)` key.
54    pub domain: &'static str,
55    /// Op type name.
56    pub op_type: &'static str,
57    /// Dispatch entry point.
58    pub invoke: OpInvokeFn,
59    /// Discriminator selecting which iterator surface returns this
60    /// entry.
61    pub kind: RegistrationKind,
62}
63
64inventory::collect!(OpRegistration);
65
66/// Look up a Custom-kind op by its `(domain, op_type)` key. Used
67/// by the engine's dispatch fallback for user-shipped ops outside
68/// the role surface.
69pub fn find_op(domain: &str, op_type: &str) -> Option<&'static OpRegistration> {
70    inventory::iter::<OpRegistration>
71        .into_iter()
72        .find(|r| r.domain == domain && r.op_type == op_type && r.kind == RegistrationKind::Custom)
73}
74
75/// Iterate every Custom-kind op registration this binary links in.
76pub fn ops() -> impl Iterator<Item = &'static OpRegistration> {
77    inventory::iter::<OpRegistration>
78        .into_iter()
79        .filter(|r| r.kind == RegistrationKind::Custom)
80}
81
82/// Iterate every Syscall-kind registration. Consumed by
83/// `Engine::register_all_framework_syscalls` at Node construction
84/// time.
85pub fn framework_syscalls() -> impl Iterator<Item = &'static OpRegistration> {
86    inventory::iter::<OpRegistration>
87        .into_iter()
88        .filter(|r| r.kind == RegistrationKind::Syscall)
89}
90
91// --- Component-role registration ----------------------------------
92//
93// Every `#[derive(bb::<Role>)]` proc-macro emits one
94// `ComponentRoleBinding` per `(component_type, role)`. The Engine
95// merges them at install time so a single `ComponentRegistration`
96// (the existing `ConcreteComponentRegistration` foundation entry +
97// these per-role bindings) carries the full picture: state
98// serialize/restore + every role the component implements.
99
100/// One concrete framework role a component declares.
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102pub enum ComponentRole {
103    /// `Index` role — vector-index Contract.
104    Index,
105    /// `Aggregator` role — federated-aggregator Contract.
106    Aggregator,
107    /// `Model` role — ML-model Contract.
108    Model,
109    /// `Codec` role — bidirectional storage-type codec Contract.
110    Codec,
111    /// `DataSource` role — data-loader Contract.
112    DataSource,
113    /// `PeerSelector` role — peer-sampling Contract.
114    PeerSelector,
115    /// `Backend` role — tensor-compute Contract.
116    Backend,
117    /// `Protocol` role — custom-protocol Contract.
118    Protocol,
119}
120
121/// Per-`(component_type, role)` inventory entry: each
122/// `#[derive(bb::<Role>)]` proc-macro emits one of these alongside
123/// the universal triple. `Node::ensure_ready` walks the channel +
124/// joins by `type_name` to compute the role bitflags per
125/// concrete component.
126pub struct ComponentRoleBinding {
127    /// `ConcreteComponent::TYPE_NAME` — the join key against
128    /// `ConcreteComponentRegistration`.
129    pub type_name: &'static str,
130    /// The role the derive corresponds to.
131    pub role: ComponentRole,
132}
133
134inventory::collect!(ComponentRoleBinding);
135
136/// Per-`(component_type, role)` inventory carrying a per-T
137/// dispatcher-registration fn pointer. Each role derive emits
138/// one of these alongside [`ComponentRoleBinding`]; `install()`
139/// walks the channel + calls `register_fn(engine)` for each
140/// bound concrete so the engine learns about every role
141/// dispatcher without the install path needing the typed `&T`.
142pub struct DispatcherRegistration {
143    /// `ConcreteComponent::TYPE_NAME` — the join key.
144    pub type_name: &'static str,
145    /// The role the registration corresponds to.
146    pub role: ComponentRole,
147    /// Per-T registration callback. The derive captures `T` at
148    /// the emit site and the fn body calls
149    /// `engine.register_<role>_dispatcher::<T>()`.
150    pub register_fn: fn(&mut crate::engine::Engine),
151}
152
153inventory::collect!(DispatcherRegistration);
154
155/// Look up the dispatcher-registration fn for
156/// `(type_name, role)`. `install()` calls this for every binding
157/// in the artifact's `BindingSpec`. Returns `None` when the role
158/// derive isn't present in this binary's inventory (e.g. a
159/// hand-implemented role trait without the derive).
160pub fn dispatcher_for(
161    type_name: &str,
162    role: ComponentRole,
163) -> Option<fn(&mut crate::engine::Engine)> {
164    inventory::iter::<DispatcherRegistration>
165        .into_iter()
166        .find(|r| r.type_name == type_name && r.role == role)
167        .map(|r| r.register_fn)
168}
169
170/// Per-concrete inventory carrying a per-T Bootstrap dispatcher-
171/// registration fn pointer. The `#[derive(bb::Concrete)]` macro
172/// emits one of these per registered concrete so the install path
173/// can register every concrete's `Bootstrap` dispatcher without the
174/// per-T downcast. Pairs with [`crate::engine::Engine::register_bootstrap_dispatcher`].
175///
176/// The derive's emitted impl falls back to the
177/// [`crate::contracts::bootstrap::Bootstrap`] trait default (no-op),
178/// so a Concrete with no manual `impl Bootstrap` still registers a
179/// dispatcher entry that drains the Component bootstrap phase
180/// cleanly. Authors override the default by hand-writing
181/// `impl Bootstrap for X` alongside the derive — the derive's
182/// `#[bb(bootstrap_override)]` attribute (parsed in
183/// `bb-derive::parse`) suppresses the default-impl emission so the
184/// two impls do not collide.
185pub struct BootstrapDispatcherRegistration {
186    /// `ConcreteComponent::TYPE_NAME` — the join key against
187    /// `ConcreteComponentRegistration`.
188    pub type_name: &'static str,
189    /// Per-T registration callback. The derive captures `T` at
190    /// the emit site and the fn body calls
191    /// `engine.register_bootstrap_dispatcher::<T>()`.
192    pub register_fn: fn(&mut crate::engine::Engine),
193}
194
195inventory::collect!(BootstrapDispatcherRegistration);
196
197/// Look up the Bootstrap dispatcher-registration fn for `type_name`.
198/// `install()` calls this for every registered concrete so the
199/// Component bootstrap fire path can dispatch through the right impl.
200/// Returns `None` when the concrete was registered without the
201/// `#[derive(bb::Concrete)]` Bootstrap-bridge emission.
202pub fn bootstrap_dispatcher_for(type_name: &str) -> Option<fn(&mut crate::engine::Engine)> {
203    inventory::iter::<BootstrapDispatcherRegistration>
204        .into_iter()
205        .find(|r| r.type_name == type_name)
206        .map(|r| r.register_fn)
207}
208
209/// Iterate every role binding for a given component type. Used by
210/// `Node::ensure_ready` to compute the bitflags + by introspection
211/// tools to discover what roles a struct implements.
212pub fn roles_for_component(type_name: &str) -> impl Iterator<Item = ComponentRole> + use<'_> {
213    inventory::iter::<ComponentRoleBinding>
214        .into_iter()
215        .filter(move |b| b.type_name == type_name)
216        .map(|b| b.role)
217}
218
219// --- Storage-type registration ------------------------------------
220//
221// Every `#[derive(bb::<Role>)]` proc-macro emits one or two
222// `StorageTypeEntry` structs (Codec emits two — one for In, one for
223// Out) into the inventory carrier. The compiler binding step (Task 9)
224// reads these to populate `BindingSlot.storage_types`. The fn-pointer
225// slot is necessary because `Storage::TYPE` is a const on a generic
226// associated type that can't be named at registry-definition time —
227// the derive captures the monomorphized path in the fn body.
228
229/// Per-concrete Storage-type entry registered by `#[derive(bb::<Role>)]`.
230///
231/// Keyed on the triple `(concrete_type_name, role_runtime, port)`.
232/// `type_node_fn` returns the `Storage::TYPE` static for the
233/// concrete's associated type at that port.
234pub struct StorageTypeEntry {
235    /// `ConcreteComponent::TYPE_NAME` — the join key against
236    /// `ConcreteComponentRegistration`.
237    pub concrete_type_name: &'static str,
238    /// Engine-side runtime trait name (`"IndexRuntime"`, etc.).
239    pub role_runtime: &'static str,
240    /// Port name: `"vector"`, `"element"`, `"tensor"`, `"sample"`,
241    /// `"in"`, or `"out"`.
242    pub port: &'static str,
243    /// Returns the `&'static TypeNode` for the role's Storage-bound
244    /// associated type at this port. The derive captures the
245    /// monomorphized path at emit time.
246    pub type_node_fn: fn() -> &'static bb_ir::types::TypeNode,
247}
248
249inventory::collect!(StorageTypeEntry);
250
251/// Iterate every registered Storage-type entry in this binary.
252pub fn iter_storage_types() -> Vec<&'static StorageTypeEntry> {
253    inventory::iter::<StorageTypeEntry>().collect()
254}
255
256/// Look up the `TypeNode` for a specific `(concrete_type_name, role_runtime, port)` triple.
257///
258/// Returns `None` when no matching entry exists in the binary's
259/// inventory (e.g. the concrete's role derive is absent or the port
260/// name is wrong).
261pub fn lookup_storage_type(
262    concrete_type_name: &str,
263    role: &str,
264    port: &str,
265) -> Option<&'static bb_ir::types::TypeNode> {
266    iter_storage_types()
267        .into_iter()
268        .find(|e| {
269            e.concrete_type_name == concrete_type_name && e.role_runtime == role && e.port == port
270        })
271        .map(|e| (e.type_node_fn)())
272}
273