logicaffeine_runtime/payload.rs
1//! `RtPayload` — the `Send`-able value subset that crosses task (and OS-thread)
2//! boundaries via channels.
3//!
4//! The interpreter and VM heaps are `Rc`-based (`!Send`). A value moved through a
5//! channel is *materialized* into this owned, allocation-self-contained form on
6//! the sending side and *rebuilt* into the receiver's heap on the other side.
7//! The marshalling between `RuntimeValue` / the VM `Value` and `RtPayload` lives
8//! in `logicaffeine-compile` (which knows those representations); this crate only
9//! defines the wire shape and guarantees it is `Send`, which is what makes the
10//! M:N work-stealing driver sound.
11
12/// A self-contained, `Send` value that can move between tasks and threads.
13///
14/// This deliberately excludes `Rc`/`RefCell`-backed and closure values: those
15/// either are not `Send` or would alias another task's heap. CRDT shared cells
16/// (which are `Arc`-backed and shared rather than moved) get their own variant
17/// when the scheduler grows them in a later phase.
18#[derive(Debug, Clone, PartialEq)]
19pub enum RtPayload {
20 /// The unit / absence value.
21 Nothing,
22 Int(i64),
23 /// An exact integer that does not fit `i64`, carried as its sign + little-endian
24 /// magnitude bytes — a dependency-free, `Send` form of `BigInt` (reconstructed
25 /// with `BigInt::from_le_bytes` on the far side).
26 BigInt { negative: bool, magnitude: Vec<u8> },
27 /// An exact rational: the signed numerator and the (always positive) denominator,
28 /// each as little-endian magnitude bytes — a dependency-free, `Send` form of
29 /// `Rational` (reconstructed with `Rational::new` on the far side). `1/3` survives
30 /// here exactly, where a JSON `0.333…` would round.
31 Rational { num_negative: bool, num_magnitude: Vec<u8>, den_magnitude: Vec<u8> },
32 /// An exact base-10 fixed-point number (money): its signed coefficient as
33 /// little-endian magnitude bytes plus the base-10 `scale` (decimal places) — a
34 /// dependency-free, `Send` form of `Decimal` (reconstructed with
35 /// `Decimal::from_le_bytes` on the far side). `19.99` survives exactly, scale and all.
36 Decimal { negative: bool, magnitude: Vec<u8>, scale: u32 },
37 /// An exact monetary amount: its `Decimal` amount (sign + LE magnitude + base-10 scale) plus the
38 /// ISO-4217 currency code. Reconstructed as `Money { Decimal::from_le_bytes, currency::by_code }`.
39 Money { negative: bool, magnitude: Vec<u8>, scale: u32, currency: String },
40 /// A 128-bit UUID — its 16 big-endian bytes verbatim. Reconstructed with `Uuid::from_bytes`.
41 Uuid([u8; 16]),
42 /// An exact complex number `re + im·i`: each part a rational (signed numerator + a
43 /// positive denominator) as little-endian magnitude bytes. Reconstructed with
44 /// `Complex::new` on the far side; `i·i = −1` survives exactly.
45 Complex {
46 re_negative: bool,
47 re_num: Vec<u8>,
48 re_den: Vec<u8>,
49 im_negative: bool,
50 im_num: Vec<u8>,
51 im_den: Vec<u8>,
52 },
53 /// An element of ℤ/nℤ: its (non-negative) residue and modulus as little-endian magnitude
54 /// bytes. Reconstructed with `Modular::new` on the far side; the ring is preserved.
55 Modular { value: Vec<u8>, modulus: Vec<u8> },
56 /// A dimensioned physical quantity: its SI-base magnitude as an exact rational (signed
57 /// numerator + positive denominator, little-endian bytes), its dimension as the exponent
58 /// vector (`dim_num[i]/dim_den[i]` over the base axes), and the display unit's symbol. The far
59 /// side rebuilds the magnitude with `Rational`, the dimension with `Dimension::from_exps`, and
60 /// resolves the unit by symbol (falling back to the SI/dimension display for compound units).
61 Quantity {
62 num_negative: bool,
63 num_magnitude: Vec<u8>,
64 den_magnitude: Vec<u8>,
65 dim_num: Vec<i32>,
66 dim_den: Vec<i32>,
67 unit_symbol: String,
68 },
69 Float(f64),
70 Bool(bool),
71 Char(char),
72 /// An owned string (not an `Rc<str>`).
73 Text(String),
74 /// A fully-materialized sequence.
75 List(Vec<RtPayload>),
76 /// A fixed heterogeneous tuple.
77 Tuple(Vec<RtPayload>),
78 /// A set, materialized as its elements.
79 Set(Vec<RtPayload>),
80 /// A map, materialized as key/value pairs.
81 Map(Vec<(RtPayload, RtPayload)>),
82 /// A struct instance: its type name and named fields.
83 Struct {
84 type_name: String,
85 fields: Vec<(String, RtPayload)>,
86 },
87 /// An inductive (sum-type) value: its type, constructor, and arguments.
88 Inductive {
89 type_name: String,
90 constructor: String,
91 args: Vec<RtPayload>,
92 },
93 /// A duration, carried in its base unit.
94 Duration(i64),
95 /// A calendar date.
96 Date(i32),
97 /// A moment in time.
98 Moment(i64),
99 /// A calendar span (months + days).
100 Span { months: i32, days: i32 },
101 /// A time-of-day.
102 Time(i64),
103 /// A fixed-width wrapping integer (`Word32`/`Word64`): its bit width (32 or 64) and the
104 /// value zero-extended to `u64`. Reconstructed via `WordVal::from_u64` on the far side.
105 Word { width: u32, bits: u64 },
106 /// A channel handle (a `Pipe`) — an opaque scheduler token. `Send` (just an
107 /// id), so a channel can be passed as a spawn argument across worker threads;
108 /// the receiving task resolves it against the one shared scheduler.
109 Chan(crate::channel::ChanId),
110 /// A spawned-task handle — likewise an opaque `Send` scheduler token.
111 TaskHandle(crate::task::TaskId),
112 /// A remote-peer handle — its canonical relay topic. A `String` is trivially
113 /// `Send`, so a peer can be passed as a spawn argument across worker threads.
114 Peer(String),
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 fn assert_send<T: Send>() {}
122
123 #[test]
124 fn rtpayload_is_send() {
125 // Compile-time guarantee: payloads can cross thread boundaries. This is
126 // the property the M:N work-stealing driver depends on (only `RtPayload`
127 // and small ids ever cross a worker boundary).
128 assert_send::<RtPayload>();
129 }
130
131 #[test]
132 fn rtpayload_roundtrips_structurally() {
133 let v = RtPayload::Struct {
134 type_name: "Point".into(),
135 fields: vec![
136 ("x".into(), RtPayload::Int(1)),
137 (
138 "y".into(),
139 RtPayload::List(vec![RtPayload::Bool(true), RtPayload::Text("hi".into())]),
140 ),
141 ],
142 };
143 assert_eq!(v.clone(), v);
144 }
145
146 #[test]
147 fn rtpayload_nested_collections() {
148 let m = RtPayload::Map(vec![
149 (RtPayload::Text("a".into()), RtPayload::Int(1)),
150 (RtPayload::Text("b".into()), RtPayload::Set(vec![RtPayload::Char('x')])),
151 ]);
152 match m {
153 RtPayload::Map(entries) => assert_eq!(entries.len(), 2),
154 _ => panic!("expected map"),
155 }
156 }
157}