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