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 ([`Program`] / [`Fn`]), 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//! | [`Program`] / [`Fn`] / [`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::{Program, FlowOutcome, Runtime, Value};
61//!
62//! let mut program = Program::new("demo");
63//! program.function("main", 0, |f| {
64//!     let a = f.load_i32(41);
65//!     let b = f.load_i32(1);
66//!     let sum = f.add(a, b);
67//!     f.return_(sum);
68//! });
69//!
70//! let rt = Runtime::new(program.build())?;
71//! let outcome = rt.spawn(0, &[])?.join();
72//! rt.shutdown();
73//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
74//! # Ok(())
75//! # }
76//! ```
77//!
78//! # Quick start — Atomic Hop (ping-pong)
79//!
80//! Hop demos need the std native table (`make_msg` / `msg_*`):
81//!
82//! ```
83//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
84//! use byteflow::{samples, std_native_table, FlowOutcome, Runtime, Value};
85//!
86//! let rt = Runtime::with_natives(samples::ping_pong(), std_native_table())?;
87//! let Some(main) = rt.function_index("main") else { return Ok(()); };
88//! let handle = rt.spawn(main, &[])?;
89//! let outcome = handle.join();
90//! rt.shutdown();
91//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
92//! # Ok(())
93//! # }
94//! ```
95//!
96//! More samples: [`samples::atomic_request_reply`], [`samples::ask_reply`],
97//! [`samples::selective_receive`], forged-sender security regressions.
98//!
99//! # Collecting a result
100//!
101//! [`FlowHandle::join`] blocks, which suits a `main` with nothing else to
102//! do. Anything holding a deadline picks its own bound instead:
103//!
104//! | Call | Waits | While the flow is still running |
105//! |------|-------|---------------------------------|
106//! | [`FlowHandle::try_join`] | never | `None` |
107//! | [`FlowHandle::join_timeout`] / [`FlowHandle::join_deadline`] | up to the bound | `None` |
108//! | [`FlowHandle::join`] | unbounded | (blocks) |
109//!
110//! ```
111//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
112//! use byteflow::{Program, FlowOutcome, Runtime, Value};
113//! use std::time::Duration;
114//!
115//! let mut program = Program::new("slow");
116//! program.function("main", 0, |f| {
117//!     let ms = f.load_i32(300);
118//!     f.sleep(ms);
119//!     let out = f.load_i32(7);
120//!     f.return_(out);
121//! });
122//!
123//! let rt = Runtime::new(program.build())?;
124//! let handle = rt.spawn(0, &[])?;
125//!
126//! // Neither of these consumes the handle or the outcome.
127//! assert!(handle.try_join().is_none());
128//! assert!(handle.join_timeout(Duration::from_millis(10)).is_none());
129//!
130//! let outcome = handle.join_timeout(Duration::from_secs(10));
131//! rt.shutdown();
132//! assert!(matches!(outcome, Some(FlowOutcome::Completed(Value::Int(7)))));
133//! # Ok(())
134//! # }
135//! ```
136//!
137//! A flow destroyed before it produced an outcome — [`Runtime::shutdown`]
138//! does not drain suspended flows — wakes its joiner with a failure instead
139//! of leaving it parked forever. See [`docs::error_model`].
140//!
141//! # Design guides (rendered on docs.rs)
142//!
143//! - [`docs::atomic_hop`] — hop protocol, Cap addressing, natives table
144//! - [`docs::mailbox`] — bounded inbox, overflow, lost-wakeup
145//! - [`docs::security`] — threat model, invariants S1–S7, roadmap
146//! - [`docs::error_model`] — fail-closed errors (no `unwrap`), bounded joins
147//! - [`docs::vm_safety`] — trust boundary: `verify` vs per-step `Fault`
148//!
149//! # What this is *not*
150//!
151//! - Not a replacement for Tokio / async Rust (no `.await` IO loop)
152//! - Not a distributed cluster runtime (single process, in-memory mailboxes)
153//! - Not a full object-capability OS (native quotas / Cap attenuation come later)
154//!
155//! Host owns I/O and policy. Byteflow owns cheap concurrency and hop delivery.
156#![deny(unsafe_code)]
157#![cfg_attr(docsrs, feature(doc_cfg))]
158
159pub mod bytecode;
160pub mod log;
161pub mod natives;
162pub mod samples;
163pub mod scheduler;
164pub mod vm;
165
166#[cfg(feature = "jit")]
167#[cfg_attr(docsrs, doc(cfg(feature = "jit")))]
168pub mod jit;
169
170/// Long-form design notes shipped inside the crate (also under `docs/` on GitHub).
171///
172/// These modules exist so [docs.rs](https://docs.rs/byteflow-actors) shows the
173/// same guides as the repository, not only API rustdoc.
174pub mod docs {
175    /// Atomic Hop: Message-only Send, FlowCap addressing, Ask, selective receive.
176    #[doc = include_str!("../docs/atomic-hop.md")]
177    pub mod atomic_hop {}
178
179    /// Security model: authenticated sender, FlowCap, invariants S1–S7.
180    #[doc = include_str!("../docs/security.md")]
181    pub mod security {}
182
183    /// Fail-closed error taxonomy and mutex policy.
184    #[doc = include_str!("../docs/error-model.md")]
185    pub mod error_model {}
186
187    /// Trust boundary: what `verify` settles statically vs what the `Vm`
188    /// checks per step, and why a fault never becomes a panic.
189    #[doc = include_str!("../docs/vm-safety.md")]
190    pub mod vm_safety {}
191
192    /// Bounded mailbox: capacity contract, overflow, anti lost-wakeup.
193    #[doc = include_str!("../docs/mailbox.md")]
194    pub mod mailbox {}
195}
196
197pub use bytecode::{
198    asm_macros, decode, disassemble, encode, verify, Chunk, Fn, FormatError, FuncId, FunctionDef,
199    Instruction, Label, Message, Opcode, Program, Reg, RegWindow, Value, VerifyError, ABI_VERSION,
200    MAGIC,
201};
202pub use natives::{std_native_map, std_native_table, std_natives};
203pub use scheduler::{
204    fault_count, next_flow_id, flow_id_from_u64, report_fault, CapId, CapRights, ChildSpec,
205    Delivery, Mailbox, MailboxBytes, MailboxCapacity, MailboxConfig, MailboxFull,
206    MailboxFullReason, MailboxStats, OverflowPolicy, WaitEpoch,
207    Flow, FlowHandle, FlowId, FlowMetrics, FlowOutcome, FlowState,
208    RestartPolicy, Runtime, RuntimeConfig, RuntimeError, RuntimeMetrics,
209    RuntimeMetricsSnapshot, RuntimeSpawner, SendError, SpawnError, Supervisor,
210    SupervisorConfig, DEFAULT_QUANTUM,
211};
212#[cfg(feature = "jit")]
213pub use scheduler::JitConfig;
214pub use vm::{
215    expect_arg, expect_bool, expect_int, expect_message, expect_u64, Fault, NativeFn,
216    NativeResult, NativeTable, NativeTableBuilder, NativeTableError, Vm, VmResult, MAX_CALL_DEPTH,
217};
218
219#[cfg(feature = "jit")]
220pub use jit::{
221    apply_exit_to_vm, force_compile, hot_threshold, run_compiled_trace, run_compiled_trace_ref,
222    run_vm_with_jit, run_vm_with_jit_runtime, sync_slots_from_vm, try_run_hot, try_run_hot_runtime,
223    CompileError, CompiledTrace, ExitReason, HotCounter, JitContext, JitEntry, JitFrame, JitReturn,
224    JitRuntime, SyncSlotsResult, TraceCache, TraceCompiler, TraceKey, TraceSpan, HOT_THRESHOLD,
225    MAX_TRACE_LENGTH, JIT_BUDGET, JIT_CONTINUE, JIT_DEOPT, JIT_EFFECT, JIT_RETURN, JIT_TRAP,
226};
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn std_native_map_has_stable_indices() {
234        let map = std_native_map();
235        assert_eq!(map.get("print"), Some(&0));
236        assert_eq!(map.get("now_ms"), Some(&1));
237        assert_eq!(map.get("make_msg"), Some(&2));
238        assert_eq!(map.get("msg_payload"), Some(&6));
239        assert_eq!(map.get("msg_reply_cap"), Some(&7));
240    }
241
242    #[test]
243    fn std_native_chunk_runs() -> Result<(), Box<dyn std::error::Error>> {
244        let mut program = Program::new("std-natives-demo");
245        program.function("main", 0, |f| {
246            let n = f.load_i32(42);
247            f.native1_on(n, 0);
248            let ms = f.call_native0(1);
249            f.return_(ms);
250        });
251
252        let chunk = program.build();
253        verify(&chunk)?;
254
255        let rt = Runtime::with_natives(chunk, std_native_table())?;
256        let outcome = rt.spawn(0, &[])?.join();
257        rt.shutdown();
258
259        match outcome {
260            FlowOutcome::Completed(Value::Int(ms)) => {
261                assert!(ms >= 0);
262                Ok(())
263            }
264            other => Err(format!("expected Completed(Value::Int(_)), got {other:?}").into()),
265        }
266    }
267}