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}