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/// Iterate every role binding for a given component type. Used by
171/// `Node::ensure_ready` to compute the bitflags + by introspection
172/// tools to discover what roles a struct implements.
173pub fn roles_for_component(type_name: &str) -> impl Iterator<Item = ComponentRole> + use<'_> {
174 inventory::iter::<ComponentRoleBinding>
175 .into_iter()
176 .filter(move |b| b.type_name == type_name)
177 .map(|b| b.role)
178}
179
180// --- Storage-type registration ------------------------------------
181//
182// Every `#[derive(bb::<Role>)]` proc-macro emits one or two
183// `StorageTypeEntry` structs (Codec emits two — one for In, one for
184// Out) into the inventory carrier. The compiler binding step (Task 9)
185// reads these to populate `BindingSlot.storage_types`. The fn-pointer
186// slot is necessary because `Storage::TYPE` is a const on a generic
187// associated type that can't be named at registry-definition time —
188// the derive captures the monomorphized path in the fn body.
189
190/// Per-concrete Storage-type entry registered by `#[derive(bb::<Role>)]`.
191///
192/// Keyed on the triple `(concrete_type_name, role_runtime, port)`.
193/// `type_node_fn` returns the `Storage::TYPE` static for the
194/// concrete's associated type at that port.
195pub struct StorageTypeEntry {
196 /// `ConcreteComponent::TYPE_NAME` — the join key against
197 /// `ConcreteComponentRegistration`.
198 pub concrete_type_name: &'static str,
199 /// Engine-side runtime trait name (`"IndexRuntime"`, etc.).
200 pub role_runtime: &'static str,
201 /// Port name: `"vector"`, `"element"`, `"tensor"`, `"sample"`,
202 /// `"in"`, or `"out"`.
203 pub port: &'static str,
204 /// Returns the `&'static TypeNode` for the role's Storage-bound
205 /// associated type at this port. The derive captures the
206 /// monomorphized path at emit time.
207 pub type_node_fn: fn() -> &'static bb_ir::types::TypeNode,
208}
209
210inventory::collect!(StorageTypeEntry);
211
212/// Iterate every registered Storage-type entry in this binary.
213pub fn iter_storage_types() -> Vec<&'static StorageTypeEntry> {
214 inventory::iter::<StorageTypeEntry>().collect()
215}
216
217/// Look up the `TypeNode` for a specific `(concrete_type_name, role_runtime, port)` triple.
218///
219/// Returns `None` when no matching entry exists in the binary's
220/// inventory (e.g. the concrete's role derive is absent or the port
221/// name is wrong).
222pub fn lookup_storage_type(
223 concrete_type_name: &str,
224 role: &str,
225 port: &str,
226) -> Option<&'static bb_ir::types::TypeNode> {
227 iter_storage_types()
228 .into_iter()
229 .find(|e| {
230 e.concrete_type_name == concrete_type_name && e.role_runtime == role && e.port == port
231 })
232 .map(|e| (e.type_node_fn)())
233}
234