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//! | [`Cap`] / [`CapRights`] / [`NativeMask`] | Holder + rights; [`Cap::attenuate`] is the only grant path |
22//! | [`QuotaConfig`] / [`FlowQuota`] | Per-flow CPU / heap / spawn-send budgets |
23//! | [`Supervisor`] | OTP strategies (`OneForOne` / `OneForAll` / `RestForOne`) |
24//! | [`std_native_table`] | `print`, `now_ms`, `make_msg` (3-arg), `msg_*`, `msg_reply_cap` |
25//!
26//! # Atomic Hop (messaging contract)
27//!
28//! Every bytecode `Send` / `Ask` carries exactly one [`Message`]:
29//!
30//! ```text
31//! Message { sender, reply_cap, request_id, tag, payload }
32//! ```
33//!
34//! - Bare `Int` / `Pid` / `Str` on `Send` → trap / [`SendError::NotAHop`]
35//! - Scheduler **stamps** `sender` (authenticated origin) and mints
36//! `reply_cap` (SEND-only Cap back to the caller)
37//! - Reply with [`std_native_table`]'s `msg_reply_cap` — **not** `msg_sender`
38//! (`Pid` is identity, not an address)
39//!
40//! Also: selective receive (`ReceiveMatch`), `Ask` / `AskTimeout` for correlated RPC.
41//!
42//! # FlowCap (addressing)
43//!
44//! | Value | Use |
45//! |-------|-----|
46//! | [`Value::Cap`] | Target of `Send` / `Ask`; from `SelfPid`, `Spawn`, or `reply_cap` |
47//! | [`Value::Pid`] | Identity inside a delivered hop (`msg_sender`) |
48//!
49//! Host [`Runtime::send`] still takes [`FlowId`] (trusted embedder path).
50//!
51//! Caps resolve only for the **holder** with sufficient rights. Derive a
52//! weaker grant with [`Fn::delegate`] / [`Cap::attenuate`] — never by
53//! copying a `CapId`. Host spawn is `ROOT`; bytecode [`Fn::spawn_confined`]
54//! starts at `NONE`.
55//!
56//! # Values (ABI v5)
57//!
58//! `Unit | Bool | Int | Float | Pid | Message | Cap | Str | Bytes`
59//!
60//! `CapId` is a 128-bit CSPRNG token. `Message.payload` is a nested
61//! [`Value`]. `Str` / `Bytes` are `Arc`-backed for cheap register/mailbox
62//! clones. They are **not** Atomic Hops by themselves.
63//!
64//! # Quick start — scalar
65//!
66//! ```
67//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
68//! use byteflow::{Program, FlowOutcome, Runtime, Value};
69//!
70//! let mut program = Program::new("demo");
71//! program.function("main", 0, |f| {
72//! let a = f.load_i32(41);
73//! let b = f.load_i32(1);
74//! let sum = f.add(a, b);
75//! f.return_(sum);
76//! });
77//!
78//! let rt = Runtime::new(program.build())?;
79//! let outcome = rt.spawn(0, &[])?.join();
80//! rt.shutdown();
81//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
82//! # Ok(())
83//! # }
84//! ```
85//!
86//! # Quick start — Atomic Hop (ping-pong)
87//!
88//! Hop demos need the std native table (`make_msg` / `msg_*`):
89//!
90//! ```
91//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
92//! use byteflow::{samples, std_native_table, FlowOutcome, Runtime, Value};
93//!
94//! let rt = Runtime::with_natives(samples::ping_pong(), std_native_table())?;
95//! let Some(main) = rt.function_index("main") else { return Ok(()); };
96//! let handle = rt.spawn(main, &[])?;
97//! let outcome = handle.join();
98//! rt.shutdown();
99//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! More samples: [`samples::atomic_request_reply`], [`samples::ask_reply`],
105//! [`samples::selective_receive`], forged-sender security regressions.
106//!
107//! # Collecting a result
108//!
109//! [`FlowHandle::join`] blocks, which suits a `main` with nothing else to
110//! do. Anything holding a deadline picks its own bound instead:
111//!
112//! | Call | Waits | While the flow is still running |
113//! |------|-------|---------------------------------|
114//! | [`FlowHandle::try_join`] | never | `None` |
115//! | [`FlowHandle::join_timeout`] / [`FlowHandle::join_deadline`] | up to the bound | `None` |
116//! | [`FlowHandle::join`] | unbounded | (blocks) |
117//!
118//! ```
119//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
120//! use byteflow::{Program, FlowOutcome, Runtime, Value};
121//! use std::time::Duration;
122//!
123//! let mut program = Program::new("slow");
124//! program.function("main", 0, |f| {
125//! let ms = f.load_i32(300);
126//! f.sleep(ms);
127//! let out = f.load_i32(7);
128//! f.return_(out);
129//! });
130//!
131//! let rt = Runtime::new(program.build())?;
132//! let handle = rt.spawn(0, &[])?;
133//!
134//! // Neither of these consumes the handle or the outcome.
135//! assert!(handle.try_join().is_none());
136//! assert!(handle.join_timeout(Duration::from_millis(10)).is_none());
137//!
138//! let outcome = handle.join_timeout(Duration::from_secs(10));
139//! rt.shutdown();
140//! assert!(matches!(outcome, Some(FlowOutcome::Completed(Value::Int(7)))));
141//! # Ok(())
142//! # }
143//! ```
144//!
145//! A flow destroyed before it produced an outcome — [`Runtime::shutdown`]
146//! does not drain suspended flows — wakes its joiner with a failure instead
147//! of leaving it parked forever. See [`docs::error_model`].
148//!
149//! # Design guides (rendered on docs.rs)
150//!
151//! - [`docs::atomic_hop`] — hop protocol, Cap addressing, natives table
152//! - [`docs::beam_mapping`] — BEAM / OTP mental model → Byteflow equivalents
153//! - [`docs::lifecycle`] — monitors, links, registry, `WAITING_SEND`
154//! - [`docs::mailbox`] — bounded inbox, overflow, lost-wakeup
155//! - [`docs::security`] — threat model, invariants S1–S7, Phase 3 (0.9.2)
156//! - [`docs::error_model`] — fail-closed errors (no `unwrap`), bounded joins
157//! - [`docs::vm_safety`] — trust boundary: `verify` vs per-step `Fault`
158//!
159//! # What this is *not*
160//!
161//! - Not a replacement for Tokio / async Rust (no `.await` IO loop)
162//! - Not a distributed cluster runtime (single process, in-memory mailboxes)
163//! - Not a full object-capability OS (no distributed revocation / Cap persistence)
164//!
165//! Host owns I/O and policy. Byteflow owns cheap concurrency and hop delivery.
166#![deny(unsafe_code)]
167#![cfg_attr(docsrs, feature(doc_cfg))]
168
169pub mod bytecode;
170pub mod log;
171pub mod natives;
172pub mod output;
173pub mod samples;
174pub mod scheduler;
175pub mod vm;
176
177#[cfg(feature = "jit")]
178#[cfg_attr(docsrs, doc(cfg(feature = "jit")))]
179pub mod jit;
180
181/// Long-form design notes shipped inside the crate (also under `docs/` on GitHub).
182///
183/// These modules exist so [docs.rs](https://docs.rs/byteflow-actors) shows the
184/// same guides as the repository, not only API rustdoc.
185pub mod docs {
186 /// Atomic Hop: Message-only Send, FlowCap addressing, Ask, selective receive.
187 #[doc = include_str!("../docs/atomic-hop.md")]
188 pub mod atomic_hop {}
189
190 /// BEAM / OTP concepts mapped to Byteflow flows, Caps, and Atomic Hop.
191 #[doc = include_str!("../docs/beam-mapping.md")]
192 pub mod beam_mapping {}
193
194 /// Flow lifecycle: monitors, links, registry, WAITING_SEND.
195 #[doc = include_str!("../docs/lifecycle.md")]
196 pub mod lifecycle {}
197
198 /// Security model: authenticated sender, FlowCap, invariants S1–S7.
199 #[doc = include_str!("../docs/security.md")]
200 pub mod security {}
201
202 /// Fail-closed error taxonomy and mutex policy.
203 #[doc = include_str!("../docs/error-model.md")]
204 pub mod error_model {}
205
206 /// Trust boundary: what `verify` settles statically vs what the `Vm`
207 /// checks per step, and why a fault never becomes a panic.
208 #[doc = include_str!("../docs/vm-safety.md")]
209 pub mod vm_safety {}
210
211 /// Bounded mailbox: capacity contract, overflow, anti lost-wakeup.
212 #[doc = include_str!("../docs/mailbox.md")]
213 pub mod mailbox {}
214}
215
216pub use bytecode::{
217 asm_macros, decode, decode_with, disassemble, encode, verify, verify_with, Cap, CapId,
218 CapIdError, CapRights, CapTarget, Chunk, Fn, ConstantKind, FormatError, FuncId, FunctionDef,
219 Instruction, Label, Message, NativeIdx, NativeMask, Opcode, Program, Reg, RegWindow,
220 RevocationCell, TrustLevel, Value, VerifyConfig, VerifyError, ABI_VERSION, MAGIC,
221 TAG_SYS_DOWN, TAG_SYS_EXIT,
222};
223pub use output::{NullSink, OutputSink, StdoutSink};
224pub use natives::{std_native, std_native_map, std_native_table, std_native_table_with, std_natives};
225pub use scheduler::{
226 fault_count, next_flow_id, flow_id_from_u64, report_fault, CapError, Capability,
227 ChildSpec, Delivery, DownEvent, FlowExitReason, FlowQuota, LifecycleError, LinkId, Mailbox, MailboxBytes,
228 MailboxCapacity, MailboxConfig, MailboxFull, MailboxFullReason, MailboxStats,
229 MonitorRef, OverflowPolicy, QuotaConfig, QuotaError, RegistryName, WaitEpoch, Flow, FlowHandle, FlowId,
230 FlowMetrics, FlowOutcome, RestartPolicy, RestartStrategy, Runtime, RuntimeConfig,
231 RuntimeError, RuntimeMetrics, RuntimeMetricsSnapshot, RuntimeSpawner, SendError,
232 SpawnError, Supervisor, SupervisorConfig, DEFAULT_QUANTUM, check_admin, check_link,
233 check_monitor, exec_delegate, AdminError, DelegateError, LinkError,
234};
235#[cfg(feature = "jit")]
236pub use scheduler::JitConfig;
237pub use vm::{
238 expect_arg, expect_bool, expect_int, expect_message, expect_u64, check_native_call,
239 Fault, NativeCallError, NativeFn, NativeGate, NativeResult, NativeTable, NativeTableBuilder,
240 NativeTableError, Vm, VmResult, MAX_CALL_DEPTH,
241};
242
243#[cfg(feature = "jit")]
244pub use jit::{
245 apply_exit_to_vm, force_compile, hot_threshold, run_compiled_trace, run_compiled_trace_ref,
246 run_vm_with_jit, run_vm_with_jit_runtime, sync_slots_from_vm, try_run_hot, try_run_hot_runtime,
247 CompileError, CompiledTrace, ExitReason, HotCounter, JitContext, JitEntry, JitFrame, JitReturn,
248 JitRuntime, SyncSlotsResult, TraceCache, TraceCompiler, TraceKey, TraceSpan, HOT_THRESHOLD,
249 MAX_TRACE_LENGTH, JIT_BUDGET, JIT_CONTINUE, JIT_DEOPT, JIT_EFFECT, JIT_RETURN, JIT_TRAP,
250};
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn std_native_map_has_stable_indices() {
258 let map = std_native_map();
259 assert_eq!(map.get("print"), Some(&0));
260 assert_eq!(map.get("now_ms"), Some(&1));
261 assert_eq!(map.get("make_msg"), Some(&2));
262 assert_eq!(map.get("msg_payload"), Some(&6));
263 assert_eq!(map.get("msg_reply_cap"), Some(&7));
264 }
265
266 #[test]
267 fn std_native_chunk_runs() -> Result<(), Box<dyn std::error::Error>> {
268 let mut program = Program::new("std-natives-demo");
269 program.function("main", 0, |f| {
270 let n = f.load_i32(42);
271 f.native1_on(n, 0);
272 let ms = f.call_native0(1);
273 f.return_(ms);
274 });
275
276 let chunk = program.build();
277 verify(&chunk)?;
278
279 let rt = Runtime::with_natives(chunk, std_native_table())?;
280 let outcome = rt.spawn(0, &[])?.join();
281 rt.shutdown();
282
283 match outcome {
284 FlowOutcome::Completed(Value::Int(ms)) => {
285 assert!(ms >= 0);
286 Ok(())
287 }
288 other => Err(format!("expected Completed(Value::Int(_)), got {other:?}").into()),
289 }
290 }
291}