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}