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