Skip to main content

bb_runtime/roles/
backend.rs

1//! `BackendRuntime` - **framework-internal** role trait for backend
2//! implementations.
3//!
4//! Backend's atomic opset IS `ai.onnx v1`, and the role op_types ARE
5//! the atomic ops. Every `ai.onnx::*` node goes straight to the
6//! atomic dispatch table.
7//!
8//! **Authoring API is the Contract trait, not this one.** Concrete
9//! backends implement [`crate::contracts::Backend`] (the 30 mandatory
10//! primitives + `execute(&GraphProto, …)`); the
11//! `#[derive(bb::Backend)]` proc-macro generates the matching
12//! `impl BackendRuntime` that bridges into the engine's
13//! atomic-dispatch table. Library makers don't write
14//! `impl BackendRuntime` by hand.
15//!
16//! Distinct from [`bb_dsl::placeholders::Backend`] - the placeholder
17//! unit struct Module authors embed as a generic placeholder slot.
18
19use crate::atomic::{AtomicOpsetDecl, DispatchResult};
20use crate::runtime::RuntimeResourceRef;
21use crate::slot_value::BackendMaterializeError;
22use crate::slot_value::SlotValue;
23
24/// Role trait for backend implementations. Universal contract per
25/// `docs/ROLES.md` §2 with no per-role methods (Backend's role
26/// opset is `ai.onnx v1` which IS its atomic opset).
27///
28/// Backends MUST minimally cover `ai.onnx v1` via `atomic_opset`.
29/// They MAY declare additional opsets (e.g. `ai.onnx v17` extensions
30/// or custom-domain ops like `mybackend.fused.MatMulAdd`) via
31/// `extension_opsets`. `Node::ready()` consults both at
32/// build time to verify every NodeProto in the loaded graphs has a
33/// covering dispatch entry.
34pub trait BackendRuntime: Send + Sync {
35    /// Backend-impl-specific error type.
36    type Error: std::error::Error + Send + Sync + 'static;
37
38    /// Atomic-op opset this impl owns at minimum - `ai.onnx v1`.
39    fn atomic_opset(&self) -> AtomicOpsetDecl;
40
41    /// Additional opsets this backend supports beyond
42    /// `atomic_opset`. Default empty - backends that ship pure
43    /// `ai.onnx v1` need not override.
44    ///
45    /// Examples of valid extensions:
46    /// - A newer `ai.onnx` version (`(ai.onnx, 17)`) declaring ops
47    ///   absent from v1.
48    /// - A custom-domain opset (`(mybackend.fused, 1)`) the backend
49    ///   recognizes via its `dispatch_atomic` body.
50    fn extension_opsets(&self) -> Vec<AtomicOpsetDecl> {
51        Vec::new()
52    }
53
54    /// Dispatch a single op or `BackendSubgraph` carrier. For
55    /// primitive ops (`Add`, `Mul`, …) each arm builds a one-node
56    /// `GraphProto` and calls `Backend::execute`. For the
57    /// `BackendSubgraph` op_type, the embedded `GraphProto` body
58    /// rides on the carrier NodeProto's `"body"` attribute and the
59    /// derive arm calls `Backend::dispatch` so user overrides
60    /// (caching, async) reach the engine.
61    fn dispatch_atomic(
62        &mut self,
63        op_type: &str,
64        inputs: &[(&str, &dyn SlotValue)],
65        ctx: &mut RuntimeResourceRef<'_>,
66    ) -> Result<DispatchResult, Self::Error>;
67
68    /// Engine-side bridge for `Backend::materialize_from_wire`. The
69    /// derive forwards `(type_hash, bytes)` through the user's
70    /// Contract method and re-boxes the typed `Self::Tensor` into a
71    /// [`BackendTensorCarrier`] wrapped in `Box<dyn SlotValue>` so
72    /// the engine can install it in the slot table without knowing
73    /// the backend's concrete tensor type. Returns
74    /// [`BackendMaterializeError`] on backend error; the engine
75    /// surfaces this as
76    /// [`crate::bus::WireReceiveErrorKind::BackendMaterializeFailed`].
77    ///
78    /// Library makers do not implement this method — `#[derive(bb::Backend)]`
79    /// emits the bridge.
80    fn materialize_from_wire(
81        &self,
82        type_hash: u64,
83        bytes: Vec<u8>,
84    ) -> Result<Box<dyn SlotValue>, BackendMaterializeError>;
85}