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`] | OTP strategies (`OneForOne` / `OneForAll` / `RestForOne`) |
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`), `Ask` / `AskTimeout` 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::beam_mapping`] — BEAM / OTP mental model → Byteflow equivalents
145//! - [`docs::lifecycle`] — monitors, links, registry, `WAITING_SEND`
146//! - [`docs::mailbox`] — bounded inbox, overflow, lost-wakeup
147//! - [`docs::security`] — threat model, invariants S1–S7, roadmap
148//! - [`docs::error_model`] — fail-closed errors (no `unwrap`), bounded joins
149//! - [`docs::vm_safety`] — trust boundary: `verify` vs per-step `Fault`
150//!
151//! # What this is *not*
152//!
153//! - Not a replacement for Tokio / async Rust (no `.await` IO loop)
154//! - Not a distributed cluster runtime (single process, in-memory mailboxes)
155//! - Not a full object-capability OS (native quotas / Cap attenuation come later)
156//!
157//! Host owns I/O and policy. Byteflow owns cheap concurrency and hop delivery.
158#![deny(unsafe_code)]
159#![cfg_attr(docsrs, feature(doc_cfg))]
160
161pub mod bytecode;
162pub mod log;
163pub mod natives;
164pub mod samples;
165pub mod scheduler;
166pub mod vm;
167
168#[cfg(feature = "jit")]
169#[cfg_attr(docsrs, doc(cfg(feature = "jit")))]
170pub mod jit;
171
172/// Long-form design notes shipped inside the crate (also under `docs/` on GitHub).
173///
174/// These modules exist so [docs.rs](https://docs.rs/byteflow-actors) shows the
175/// same guides as the repository, not only API rustdoc.
176pub mod docs {
177 /// Atomic Hop: Message-only Send, FlowCap addressing, Ask, selective receive.
178 #[doc = include_str!("../docs/atomic-hop.md")]
179 pub mod atomic_hop {}
180
181 /// BEAM / OTP concepts mapped to Byteflow flows, Caps, and Atomic Hop.
182 #[doc = include_str!("../docs/beam-mapping.md")]
183 pub mod beam_mapping {}
184
185 /// Flow lifecycle: monitors, links, registry, WAITING_SEND.
186 #[doc = include_str!("../docs/lifecycle.md")]
187 pub mod lifecycle {}
188
189 /// Security model: authenticated sender, FlowCap, invariants S1–S7.
190 #[doc = include_str!("../docs/security.md")]
191 pub mod security {}
192
193 /// Fail-closed error taxonomy and mutex policy.
194 #[doc = include_str!("../docs/error-model.md")]
195 pub mod error_model {}
196
197 /// Trust boundary: what `verify` settles statically vs what the `Vm`
198 /// checks per step, and why a fault never becomes a panic.
199 #[doc = include_str!("../docs/vm-safety.md")]
200 pub mod vm_safety {}
201
202 /// Bounded mailbox: capacity contract, overflow, anti lost-wakeup.
203 #[doc = include_str!("../docs/mailbox.md")]
204 pub mod mailbox {}
205}
206
207pub use bytecode::{
208 asm_macros, decode, disassemble, encode, verify, Chunk, Fn, FormatError, FuncId, FunctionDef,
209 Instruction, Label, Message, Opcode, Program, Reg, RegWindow, Value, VerifyError, ABI_VERSION,
210 MAGIC, TAG_SYS_DOWN, TAG_SYS_EXIT,
211};
212pub use natives::{std_native, std_native_map, std_native_table, std_natives};
213pub use scheduler::{
214 fault_count, next_flow_id, flow_id_from_u64, report_fault, CapId, CapRights, ChildSpec,
215 Delivery, DownEvent, FlowExitReason, LifecycleError, LinkId, Mailbox, MailboxBytes,
216 MailboxCapacity, MailboxConfig, MailboxFull, MailboxFullReason, MailboxStats,
217 MonitorRef, OverflowPolicy, RegistryName, WaitEpoch, Flow, FlowHandle, FlowId,
218 FlowMetrics, FlowOutcome, RestartPolicy, RestartStrategy, Runtime, RuntimeConfig,
219 RuntimeError, RuntimeMetrics, RuntimeMetricsSnapshot, RuntimeSpawner, SendError,
220 SpawnError, Supervisor, SupervisorConfig, DEFAULT_QUANTUM,
221};
222#[cfg(feature = "jit")]
223pub use scheduler::JitConfig;
224pub use vm::{
225 expect_arg, expect_bool, expect_int, expect_message, expect_u64, Fault, NativeFn,
226 NativeResult, NativeTable, NativeTableBuilder, NativeTableError, Vm, VmResult, MAX_CALL_DEPTH,
227};
228
229#[cfg(feature = "jit")]
230pub use jit::{
231 apply_exit_to_vm, force_compile, hot_threshold, run_compiled_trace, run_compiled_trace_ref,
232 run_vm_with_jit, run_vm_with_jit_runtime, sync_slots_from_vm, try_run_hot, try_run_hot_runtime,
233 CompileError, CompiledTrace, ExitReason, HotCounter, JitContext, JitEntry, JitFrame, JitReturn,
234 JitRuntime, SyncSlotsResult, TraceCache, TraceCompiler, TraceKey, TraceSpan, HOT_THRESHOLD,
235 MAX_TRACE_LENGTH, JIT_BUDGET, JIT_CONTINUE, JIT_DEOPT, JIT_EFFECT, JIT_RETURN, JIT_TRAP,
236};
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn std_native_map_has_stable_indices() {
244 let map = std_native_map();
245 assert_eq!(map.get("print"), Some(&0));
246 assert_eq!(map.get("now_ms"), Some(&1));
247 assert_eq!(map.get("make_msg"), Some(&2));
248 assert_eq!(map.get("msg_payload"), Some(&6));
249 assert_eq!(map.get("msg_reply_cap"), Some(&7));
250 }
251
252 #[test]
253 fn std_native_chunk_runs() -> Result<(), Box<dyn std::error::Error>> {
254 let mut program = Program::new("std-natives-demo");
255 program.function("main", 0, |f| {
256 let n = f.load_i32(42);
257 f.native1_on(n, 0);
258 let ms = f.call_native0(1);
259 f.return_(ms);
260 });
261
262 let chunk = program.build();
263 verify(&chunk)?;
264
265 let rt = Runtime::with_natives(chunk, std_native_table())?;
266 let outcome = rt.spawn(0, &[])?.join();
267 rt.shutdown();
268
269 match outcome {
270 FlowOutcome::Completed(Value::Int(ms)) => {
271 assert!(ms >= 0);
272 Ok(())
273 }
274 other => Err(format!("expected Completed(Value::Int(_)), got {other:?}").into()),
275 }
276 }
277}