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//! | [`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}