Skip to main content

byteflow/
lib.rs

1//! Byteflow — embeddable **flow** runtime (package **`byteflow-actors`**).
2//!
3//! Not a language, not Tokio, not a JVM. You assemble register bytecode in
4//! host Rust ([`ChunkBuilder`]), spawn many lightweight **flows** on an M:N
5//! scheduler, and they talk through mailboxes with a strict hop protocol.
6//!
7//! Dependents write `use byteflow::...` (crate name) while crates.io lists
8//! the package as [`byteflow-actors`](https://crates.io/crates/byteflow-actors).
9//!
10//! # What you get
11//!
12//! | Piece | Role |
13//! |-------|------|
14//! | [`ChunkBuilder`] / [`Opcode`] | Assemble `.bf` programs in Rust (no source language) |
15//! | [`verify`] | Static gate for untrusted chunks — mandatory, not advisory |
16//! | [`Vm`] / [`VmResult`] | Per-flow register interpreter; effects hand off to the scheduler |
17//! | [`Runtime`] | Worker pool + timer; spawn / join / host [`Runtime::send`] |
18//! | [`FlowHandle`] | Collect an outcome: blocking [`FlowHandle::join`] or a bounded form |
19//! | [`Value::Message`] | **Atomic Hop** envelope — the only value allowed on `Send` / `Ask` |
20//! | [`Value::Cap`] | **FlowCap** address for bytecode delivery (`Send` / `Ask` targets) |
21//! | [`Supervisor`] | Restart policies when a flow fails |
22//! | [`std_native_table`] | `print`, `now_ms`, `make_msg`, `msg_*`, `msg_reply_cap` |
23//!
24//! # Atomic Hop (messaging contract)
25//!
26//! Every bytecode `Send` / `Ask` carries exactly one [`Message`]:
27//!
28//! ```text
29//! Message { sender, reply_cap, request_id, tag, payload }
30//! ```
31//!
32//! - Bare `Int` / `Pid` / `Str` on `Send` → trap / [`SendError::NotAHop`]
33//! - Scheduler **stamps** `sender` (authenticated origin) and mints
34//!   `reply_cap` (SEND-only Cap back to the caller)
35//! - Reply with [`std_native_table`]'s `msg_reply_cap` — **not** `msg_sender`
36//!   (`Pid` is identity, not an address)
37//!
38//! Also: selective receive (`ReceiveMatch`), and `Ask` for correlated RPC.
39//!
40//! # FlowCap (addressing)
41//!
42//! | Value | Use |
43//! |-------|-----|
44//! | [`Value::Cap`] | Target of `Send` / `Ask`; from `SelfPid`, `Spawn`, or `reply_cap` |
45//! | [`Value::Pid`] | Identity inside a delivered hop (`msg_sender`) |
46//!
47//! Host [`Runtime::send`] still takes [`FlowId`] (trusted embedder path).
48//!
49//! # Values (ABI v4)
50//!
51//! `Unit | Bool | Int | Float | Pid | Message | Cap | Str | Bytes`
52//!
53//! `Str` / `Bytes` are `Arc`-backed for cheap register/mailbox clones. They
54//! are **not** Atomic Hops by themselves.
55//!
56//! # Quick start — scalar
57//!
58//! ```
59//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
60//! use byteflow::{ChunkBuilder, Opcode, FlowOutcome, Runtime, Value};
61//!
62//! let mut b = ChunkBuilder::new("demo");
63//! b.begin_function("main", 0, 2);
64//! b.emit_load_imm(0, 41);
65//! b.emit_load_imm(1, 1);
66//! b.emit_binop(Opcode::Add, 0, 0, 1);
67//! b.emit_return(0);
68//!
69//! let rt = Runtime::new(b.finish())?;
70//! let outcome = rt.spawn(0, &[])?.join();
71//! rt.shutdown();
72//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! # Quick start — Atomic Hop (ping-pong)
78//!
79//! Hop demos need the std native table (`make_msg` / `msg_*`):
80//!
81//! ```
82//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
83//! use byteflow::{samples, std_native_table, FlowOutcome, Runtime, Value};
84//!
85//! let rt = Runtime::with_natives(samples::ping_pong(), std_native_table())?;
86//! let Some(main) = rt.function_index("main") else { return Ok(()); };
87//! let handle = rt.spawn(main, &[])?;
88//! let outcome = handle.join();
89//! rt.shutdown();
90//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
91//! # Ok(())
92//! # }
93//! ```
94//!
95//! More samples: [`samples::atomic_request_reply`], [`samples::ask_reply`],
96//! [`samples::selective_receive`], forged-sender security regressions.
97//!
98//! # Collecting a result
99//!
100//! [`FlowHandle::join`] blocks, which suits a `main` with nothing else to
101//! do. Anything holding a deadline picks its own bound instead:
102//!
103//! | Call | Waits | While the flow is still running |
104//! |------|-------|---------------------------------|
105//! | [`FlowHandle::try_join`] | never | `None` |
106//! | [`FlowHandle::join_timeout`] / [`FlowHandle::join_deadline`] | up to the bound | `None` |
107//! | [`FlowHandle::join`] | unbounded | (blocks) |
108//!
109//! ```
110//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
111//! use byteflow::{ChunkBuilder, FlowOutcome, Runtime, Value};
112//! use std::time::Duration;
113//!
114//! let mut b = ChunkBuilder::new("slow");
115//! b.begin_function("main", 0, 2);
116//! b.emit_load_imm(0, 300);
117//! b.emit_sleep(0);
118//! b.emit_load_imm(0, 7);
119//! b.emit_return(0);
120//!
121//! let rt = Runtime::new(b.finish())?;
122//! let handle = rt.spawn(0, &[])?;
123//!
124//! // Neither of these consumes the handle or the outcome.
125//! assert!(handle.try_join().is_none());
126//! assert!(handle.join_timeout(Duration::from_millis(10)).is_none());
127//!
128//! let outcome = handle.join_timeout(Duration::from_secs(10));
129//! rt.shutdown();
130//! assert!(matches!(outcome, Some(FlowOutcome::Completed(Value::Int(7)))));
131//! # Ok(())
132//! # }
133//! ```
134//!
135//! A flow destroyed before it produced an outcome — [`Runtime::shutdown`]
136//! does not drain suspended flows — wakes its joiner with a failure instead
137//! of leaving it parked forever. See [`docs::error_model`].
138//!
139//! # Design guides (rendered on docs.rs)
140//!
141//! - [`docs::atomic_hop`] — hop protocol, Cap addressing, natives table
142//! - [`docs::mailbox`] — bounded inbox, overflow, lost-wakeup
143//! - [`docs::security`] — threat model, invariants S1–S7, roadmap
144//! - [`docs::error_model`] — fail-closed errors (no `unwrap`), bounded joins
145//! - [`docs::vm_safety`] — trust boundary: `verify` vs per-step `Fault`
146//!
147//! # What this is *not*
148//!
149//! - Not a replacement for Tokio / async Rust (no `.await` IO loop)
150//! - Not a distributed cluster runtime (single process, in-memory mailboxes)
151//! - Not a full object-capability OS (native quotas / Cap attenuation come later)
152//!
153//! Host owns I/O and policy. Byteflow owns cheap concurrency and hop delivery.
154#![forbid(unsafe_code)]
155#![cfg_attr(docsrs, feature(doc_cfg))]
156
157pub mod bytecode;
158pub mod log;
159pub mod natives;
160pub mod samples;
161pub mod scheduler;
162pub mod vm;
163
164/// Long-form design notes shipped inside the crate (also under `docs/` on GitHub).
165///
166/// These modules exist so [docs.rs](https://docs.rs/byteflow-actors) shows the
167/// same guides as the repository, not only API rustdoc.
168pub mod docs {
169    /// Atomic Hop: Message-only Send, FlowCap addressing, Ask, selective receive.
170    #[doc = include_str!("../docs/atomic-hop.md")]
171    pub mod atomic_hop {}
172
173    /// Security model: authenticated sender, FlowCap, invariants S1–S7.
174    #[doc = include_str!("../docs/security.md")]
175    pub mod security {}
176
177    /// Fail-closed error taxonomy and mutex policy.
178    #[doc = include_str!("../docs/error-model.md")]
179    pub mod error_model {}
180
181    /// Trust boundary: what `verify` settles statically vs what the `Vm`
182    /// checks per step, and why a fault never becomes a panic.
183    #[doc = include_str!("../docs/vm-safety.md")]
184    pub mod vm_safety {}
185
186    /// Bounded mailbox: capacity contract, overflow, anti lost-wakeup.
187    #[doc = include_str!("../docs/mailbox.md")]
188    pub mod mailbox {}
189}
190
191pub use bytecode::{
192    asm_macros, decode, disassemble, encode, verify, Chunk, ChunkBuilder, FormatError,
193    FunctionDef, Instruction, Label, Message, Opcode, Value, VerifyError, ABI_VERSION, MAGIC,
194};
195pub use natives::{std_native_map, std_native_table, std_natives};
196pub use scheduler::{
197    fault_count, next_flow_id, flow_id_from_u64, report_fault, CapId, CapRights, ChildSpec,
198    Delivery, Mailbox, MailboxBytes, MailboxCapacity, MailboxConfig, MailboxFull,
199    MailboxFullReason, MailboxStats, OverflowPolicy, WaitEpoch,
200    Flow, FlowHandle, FlowId, FlowMetrics, FlowOutcome, FlowState,
201    RestartPolicy, Runtime, RuntimeConfig, RuntimeError, RuntimeMetrics,
202    RuntimeMetricsSnapshot, RuntimeSpawner, SendError, SpawnError, Supervisor,
203    SupervisorConfig, DEFAULT_QUANTUM,
204};
205pub use vm::{
206    expect_arg, expect_bool, expect_int, expect_message, expect_u64, Fault, NativeFn,
207    NativeResult, NativeTable, NativeTableBuilder, NativeTableError, Vm, VmResult, MAX_CALL_DEPTH,
208};
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn std_native_map_has_stable_indices() {
216        let map = std_native_map();
217        assert_eq!(map.get("print"), Some(&0));
218        assert_eq!(map.get("now_ms"), Some(&1));
219        assert_eq!(map.get("make_msg"), Some(&2));
220        assert_eq!(map.get("msg_payload"), Some(&6));
221        assert_eq!(map.get("msg_reply_cap"), Some(&7));
222    }
223
224    #[test]
225    fn chunk_builder_with_std_natives() -> Result<(), Box<dyn std::error::Error>> {
226        let mut b = ChunkBuilder::new("std-natives-demo");
227        b.begin_function("main", 0, 2);
228        b.emit_load_imm(0, 42);
229        b.emit_call_native(0, 0, 1);
230        b.emit_call_native(1, 1, 0);
231        b.emit_return(1);
232
233        let chunk = b.finish();
234        verify(&chunk)?;
235
236        let rt = Runtime::with_natives(chunk, std_native_table())?;
237        let outcome = rt.spawn(0, &[])?.join();
238        rt.shutdown();
239
240        match outcome {
241            FlowOutcome::Completed(Value::Int(ms)) => {
242                assert!(ms >= 0);
243                Ok(())
244            }
245            other => Err(format!("expected Completed(Value::Int(_)), got {other:?}").into()),
246        }
247    }
248}