Skip to main content

bb_ops/syscalls/composite/
unbundle.rs

1//! `composite.Unbundle` - decompose a [`CompositeValue`] into N
2//! per-child outputs.
3//!
4//! Reads two attributes the DSL recorder stamps:
5//!
6//! - `ai.bytesandbrains.composite.child_count` (INT) - declared number
7//!   of child slots, validated against the incoming envelope's length.
8//! - `ai.bytesandbrains.composite.child_types` (STRING) - comma-separated
9//!   TypeNode denotations naming each child's declared output type. The
10//!   compiler's TypeSolver narrows downstream consumer inputs through
11//!   these denotations regardless of the in-process carrier shape.
12//!
13//! Each output port is named `child_{i}` to match the `Bundle` input
14//! convention. The op emits each child as its original concrete
15//! `SlotValue` carrier (`clone_boxed` of the stored child) - downstream
16//! consumers downcast directly to `T` instead of bincode-decoding a
17//! `BytesValue` against the declared denotation.
18
19use bb_ir::proto::onnx::NodeProto;
20use bb_runtime::atomic::DispatchResult;
21use bb_runtime::bus::{OpError, OpErrorKind};
22use bb_runtime::runtime::RuntimeResourceRef;
23use bb_runtime::slot_value::SlotValue;
24use bb_runtime::syscall::values::CompositeValue;
25
26/// `(domain, op_type)` registration key.
27pub const DOMAIN: &str = "ai.bytesandbrains.composite";
28/// Op type name.
29pub const OP_TYPE: &str = "Unbundle";
30/// Input port carrying the [`CompositeValue`].
31pub const PORT_BUNDLE: &str = "bundle";
32/// Attribute name (INT) declaring the expected child count.
33pub const ATTR_CHILD_COUNT: &str = "ai.bytesandbrains.composite.child_count";
34/// Attribute name (STRING) carrying the comma-separated TypeNode
35/// denotations per child.
36pub const ATTR_CHILD_TYPES: &str = "ai.bytesandbrains.composite.child_types";
37
38/// Invoke fn - validate the incoming [`CompositeValue`] against the
39/// declared child count, emit each typed child on `child_{i}` via
40/// `SlotValue::clone_boxed`. Downstream consumers downcast directly
41/// to the concrete type the graph contract guarantees at each child
42/// site.
43pub fn invoke(
44    node: &NodeProto,
45    inputs: &[(&str, &dyn SlotValue)],
46    _ctx: &mut RuntimeResourceRef<'_>,
47) -> Result<DispatchResult, OpError> {
48    let composite = downcast_composite(inputs)?;
49    let declared = declared_child_count(node)?;
50    if composite.children.len() != declared {
51        return Err(OpError {
52            kind: OpErrorKind::ExecutionFailed,
53            reason: "unbundle_child_count_mismatch",
54            detail: format!(
55                "composite.Unbundle: envelope carries {} children, declared {}",
56                composite.children.len(),
57                declared
58            ),
59        });
60    }
61    let mut outs: Vec<(String, Box<dyn SlotValue>)> = Vec::with_capacity(declared);
62    for (i, child) in composite.children.iter().enumerate() {
63        outs.push((format!("child_{i}"), child.clone_boxed()));
64    }
65    Ok(DispatchResult::Immediate(outs))
66}
67
68fn downcast_composite<'a>(
69    inputs: &'a [(&str, &dyn SlotValue)],
70) -> Result<&'a CompositeValue, OpError> {
71    let (_, value) = inputs
72        .iter()
73        .find(|(n, _)| *n == PORT_BUNDLE)
74        .ok_or_else(|| OpError {
75            kind: OpErrorKind::MissingSlot,
76            reason: "missing_bundle",
77            detail: "composite.Unbundle: required input `bundle` is absent".into(),
78        })?;
79    value
80        .as_any()
81        .downcast_ref::<CompositeValue>()
82        .ok_or_else(|| OpError {
83            kind: OpErrorKind::TypeMismatch,
84            reason: "expected_composite",
85            detail: "composite.Unbundle: input `bundle` is not a CompositeValue".into(),
86        })
87}
88
89fn declared_child_count(node: &NodeProto) -> Result<usize, OpError> {
90    let attr = node
91        .attribute
92        .iter()
93        .find(|a| a.name == ATTR_CHILD_COUNT)
94        .ok_or_else(|| OpError {
95            kind: OpErrorKind::MissingSlot,
96            reason: "missing_child_count",
97            detail: format!("composite.Unbundle: missing `{ATTR_CHILD_COUNT}` attribute"),
98        })?;
99    if attr.i < 1 {
100        return Err(OpError {
101            kind: OpErrorKind::ExecutionFailed,
102            reason: "child_count_below_one",
103            detail: format!(
104                "composite.Unbundle: `{ATTR_CHILD_COUNT}` must be >= 1, got {}",
105                attr.i
106            ),
107        });
108    }
109    Ok(attr.i as usize)
110}
111
112
113bb_derive::register_op! {
114    domain: "ai.bytesandbrains.composite",
115    op_type: "Unbundle",
116    invoke: invoke,
117}