Skip to main content

bb_runtime/syscall/
values.rs

1//! Typed primitive carriers shipped by the framework.
2//!
3//! Each typed carrier wraps one well-known framework primitive
4//! (`PeerId`, `CommandId`, etc.). Slot-table residency comes from
5//! the blanket impl on [`crate::slot_value::SlotValue`] - the
6//! carriers derive `Serialize + Deserialize + Clone` and that is
7//! the entire contract. Downstream consumers downcast to the
8//! concrete carrier the graph guarantees at each site.
9
10use serde::de::{self, SeqAccess, Visitor};
11use serde::ser::{SerializeSeq, SerializeTuple};
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13
14use bb_ir::slot_value::{wire_decoder_registry, SlotValue as IrSlotValue};
15use bb_ir::types::{
16    TYPE_ADDRESS_VEC, TYPE_BYTES, TYPE_COMPOSITE, TYPE_MULTIADDRESS, TYPE_PEER_ID,
17    TYPE_PEER_ID_VEC, TYPE_TRIGGER, TYPE_WIRE_REQ_ID,
18};
19use bb_ir::{register_charged_bytes, register_type_node};
20
21use crate::framework::Address;
22use crate::ids::{CommandId, PeerId};
23
24/// Zero-payload signal value. Used for `Trigger` outputs from
25/// syscall ops (Pulse, OnTrigger, Threshold, Interval, After, etc).
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
27pub struct TriggerValue;
28
29/// Typed carrier for a `PeerId`.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub struct PeerIdValue(pub PeerId);
32
33/// Typed carrier for a `Vec<PeerId>` — the cardinal peer-list shape
34/// flowing into a `wire.Send`. Sampler / DHT / static-constant
35/// Components produce this; the Send fans out one envelope per
36/// peer. An empty vec is a valid no-op (zero envelopes shipped).
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub struct PeerIdVecValue(pub Vec<PeerId>);
39
40/// Typed carrier for a `CommandId`.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct CommandIdValue(pub CommandId);
43
44/// Typed carrier for a wall-clock timestamp (nanoseconds since
45/// epoch).
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct TimestampValue(pub u64);
48
49/// Typed carrier for a `wire_req_id` (request/response correlation
50/// id minted by the wire syscall).
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
52pub struct WireReqIdValue(pub u64);
53
54/// Typed carrier for a `CorrelateTag` correlation token.
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
56pub struct CorrelationTokenValue(pub u64);
57
58/// Typed carrier for a multiaddr `Address`.
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub struct AddressValue(pub Address);
61
62/// Typed carrier for `Vec<Address>` — the ordered local-address bag
63/// installed via `bb::install`, the payload shape of the multi-address
64/// `AddressBook` syscalls, and the value flowing into `wire.Send`'s
65/// `src_peer_addresses` stamp. Ordering reflects caller preference;
66/// the receiver merges into the AddressBook with positional dedup.
67#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
68pub struct AddressVecValue(pub Vec<Address>);
69
70/// Generic byte payload carrier.
71#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
72pub struct BytesValue(pub Vec<u8>);
73
74/// Envelope holding N typed child slot values bundled into one
75/// wire-eligible slot. Children are owned typed carriers; in-process
76/// Bundle/Unbundle round-trip preserves the concrete type without a
77/// bincode hop. The wire codec (Commit 2) serialises children as
78/// `(type_hash, child.to_wire_bytes())` tuples and the receiver
79/// materialises typed children via the decoder registry.
80pub struct CompositeValue {
81    /// Owned typed children, positionally aligned with the recorded
82    /// `Bundle` input slots. Each entry is the original concrete
83    /// `SlotValue` cloned via `clone_boxed`.
84    pub children: Vec<Box<dyn IrSlotValue>>,
85}
86
87impl Clone for CompositeValue {
88    fn clone(&self) -> Self {
89        Self {
90            children: self.children.iter().map(|c| c.clone_boxed()).collect(),
91        }
92    }
93}
94
95impl std::fmt::Debug for CompositeValue {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        // Trait-object children have no Debug bound; surface count
98        // and per-child type_hash so test failures are diagnosable
99        // without forcing a Debug bound onto every SlotValue impl.
100        let hashes: Vec<u64> = self.children.iter().map(|c| c.type_hash()).collect();
101        f.debug_struct("CompositeValue")
102            .field("children_len", &self.children.len())
103            .field("child_type_hashes", &hashes)
104            .finish()
105    }
106}
107
108// Wire codec: emit each child as a `(type_hash, child_bytes)` pair,
109// length-prefixed by the serde sequence header. The receiver looks
110// up each type_hash in the `wire_decoder_registry` and materialises
111// the typed child. Bincode's sequence + tuple encoding gives a
112// compact length-prefixed layout without a hand-rolled framing
113// scheme.
114impl Serialize for CompositeValue {
115    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
116        let mut seq = ser.serialize_seq(Some(self.children.len()))?;
117        for child in &self.children {
118            let bytes = child.to_wire_bytes().map_err(|e| {
119                serde::ser::Error::custom(format!("CompositeValue child encode: {e}"))
120            })?;
121            // Each entry is a 2-tuple `(u64, Vec<u8>)` so the
122            // receiver can read the type_hash before consuming the
123            // payload.
124            seq.serialize_element(&WireChild {
125                type_hash: child.type_hash(),
126                bytes,
127            })?;
128        }
129        seq.end()
130    }
131}
132
133impl<'de> Deserialize<'de> for CompositeValue {
134    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
135        struct ChildrenVisitor;
136        impl<'de> Visitor<'de> for ChildrenVisitor {
137            type Value = Vec<Box<dyn IrSlotValue>>;
138            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139                write!(f, "a sequence of (type_hash, bytes) child entries")
140            }
141            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
142                let mut out: Vec<Box<dyn IrSlotValue>> =
143                    Vec::with_capacity(seq.size_hint().unwrap_or(0));
144                while let Some(entry) = seq.next_element::<WireChild>()? {
145                    let decoder = wire_decoder_registry()
146                        .get(&entry.type_hash)
147                        .copied()
148                        .ok_or_else(|| {
149                            de::Error::custom(format!(
150                                "CompositeValue child decode: no decoder registered for type_hash {:#018x}",
151                                entry.type_hash,
152                            ))
153                        })?;
154                    let child = decoder(&entry.bytes).map_err(|e| {
155                        de::Error::custom(format!("CompositeValue child decode: {e}"))
156                    })?;
157                    out.push(child);
158                }
159                Ok(out)
160            }
161        }
162        let children = de.deserialize_seq(ChildrenVisitor)?;
163        Ok(CompositeValue { children })
164    }
165}
166
167/// On-wire shape of a single child: type discriminator + payload.
168/// Serialized as a 2-tuple so the bincode layout is `(u64, Vec<u8>)`
169/// with no field-name overhead.
170struct WireChild {
171    type_hash: u64,
172    bytes: Vec<u8>,
173}
174
175impl Serialize for WireChild {
176    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
177        let mut t = ser.serialize_tuple(2)?;
178        t.serialize_element(&self.type_hash)?;
179        t.serialize_element(&self.bytes)?;
180        t.end()
181    }
182}
183
184impl<'de> Deserialize<'de> for WireChild {
185    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
186        // Reuse the derived serde for `(u64, Vec<u8>)`; the explicit
187        // tuple visitor would duplicate that logic without gain.
188        let (type_hash, bytes) = <(u64, Vec<u8>)>::deserialize(de)?;
189        Ok(WireChild { type_hash, bytes })
190    }
191}
192
193/// Generic `u64` carrier - used for tests and `bb.u64`-typed slot
194/// values that don't have a more specific carrier above.
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
196pub struct U64Value(pub u64);
197
198// ---- Type-node registrations ------------------------------------
199//
200// Each typed carrier wraps a specific lattice concrete leaf.
201// `SlotValue::runtime_type` consults this registry at runtime; the
202// compiler's TypeSolver seeds inputs from these mappings.
203
204register_type_node!(TriggerValue, &TYPE_TRIGGER);
205register_type_node!(PeerIdValue, &TYPE_PEER_ID);
206register_type_node!(PeerIdVecValue, &TYPE_PEER_ID_VEC);
207register_type_node!(WireReqIdValue, &TYPE_WIRE_REQ_ID);
208register_type_node!(BytesValue, &TYPE_BYTES);
209register_type_node!(AddressValue, &TYPE_MULTIADDRESS);
210register_type_node!(AddressVecValue, &TYPE_ADDRESS_VEC);
211register_type_node!(CompositeValue, &TYPE_COMPOSITE);
212
213// `BytesValue` is the framework-side ingress carrier — every wire
214// payload that lands as a framework carrier (not a backend-mediated
215// tensor) flows through it. Reporting `self.0.len()` lets the
216// slot-table writer release the wire-byte charge on overwrite /
217// eviction without the caller having to retain the count
218// separately. The default `SlotValue::charged_bytes` body returns
219// 0 for every other concrete carrier.
220register_charged_bytes!(BytesValue, |b: &BytesValue| b.0.len());
221