bytecode_lang/lib.rs
1//! # bytecode_lang
2//!
3//! LSB, the LexerSketch bytecode format: one register-machine instruction
4//! set for every language and every execution tier, its module model, a
5//! versioned binary encoding, a budgeted decoder, a deterministic
6//! disassembler, and a builder that resolves branch labels.
7//!
8//! The VM (bvm-lang 2.0) executes LSB, code generators (codegen-lang 2.0)
9//! emit it, program images (loader-lang) store it, and the debugger
10//! (dap-lang) reads its line tables. This crate owns the format only; it
11//! executes nothing.
12//!
13//! ## Quick start
14//!
15//! Build a function, encode the module, decode it back, and read the listing:
16//!
17//! ```
18//! use bytecode_lang::{decode, disassemble, encode, Inst, IntOp, IntTy, ModuleBuilder, ValType};
19//!
20//! let mut m = ModuleBuilder::new();
21//! let mut f = m.function("add", &[ValType::I64, ValType::I64], &[ValType::I64]);
22//! let (a, b) = (f.param(0), f.param(1));
23//! let sum = f.reg(ValType::I64);
24//! f.emit(Inst::IAdd { dst: sum, lhs: a, rhs: b, op: IntOp::new(IntTy::I64) });
25//! f.ret(sum);
26//! m.add_function(f)?;
27//! let module = m.finish()?;
28//!
29//! let bytes = encode(&module);
30//! let back = decode(&bytes)?;
31//! assert_eq!(back, module);
32//! assert!(disassemble(&back).contains("iadd.i64 r2, r0, r1"));
33//! # Ok::<(), Box<dyn std::error::Error>>(())
34//! ```
35//!
36//! ## The model
37//!
38//! - **Registers, typed.** Every function has a frame of 64-bit registers,
39//! each with a declared [`ValType`]: unboxed scalars for static languages,
40//! [`ValType::Dyn`] for dynamic values, and references. Declared types make
41//! GC roots exact and let the verifier check every operand.
42//! - **Eight-byte instructions.** [`Inst`] is a `Copy` enum of eight bytes;
43//! a function body is a flat `&[Inst]` that an interpreter walks without
44//! re-parsing. Branch targets are resolved instruction indices.
45//! - **Policies on every operation.** Integer instructions carry an
46//! [`IntOp`] (the operand type plus the overflow, division-by-zero, and
47//! shift policies), dynamic ones a [`Policy`], float-to-integer
48//! conversions a [`FloatConv`], so every tier computes the same result or
49//! raises the same [`ErrorKind`].
50//! - **Dynamic languages built in.** `dyn` arithmetic and comparisons with a
51//! numeric fast path, ordered hash maps, property access, iteration, and
52//! per-module [`Hook`]s that let a language supply its own semantics for
53//! the slow path. `overflow = promote` gives PHP's int-to-float arithmetic,
54//! `shift = saturate` its shifts.
55//! - **PHP's value and reference semantics.** `dsep_index` separates a nested
56//! array only when it may be shared; references into map slots and
57//! properties (`dref_index`, `dbind_index`) are transparent in their
58//! slots and survive copies as PHP's do.
59//! - **Dynamic calls with real signatures.** A function or import may carry a
60//! [`ParamList`] (names, rest parameters, defaults, by-reference
61//! parameters); `dcall_shape` passes named and spread arguments through a
62//! [`CallShape`], and [`ParamList::bind`] is the one binding rule every
63//! tier applies.
64//! - **Coroutines.** Stackful coroutines (`coro_new`, `yield`, `await`,
65//! `resume`, `spawn`) for generators, fibers, and async tasks; see
66//! [`CoroState`].
67//!
68//! The normative description of every instruction is `specs/LSB.md` in the
69//! LexerSketch plan; `docs/API.md` documents every public item.
70//!
71//! ## Guarantees
72//!
73//! - [`decode`] never panics on any input, never recurses, and allocates no
74//! more than its [`Limits`] and the input length justify.
75//! - `decode(&encode(&m)) == Ok(m)` for every module, and
76//! `encode(&decode(bytes)?) == bytes` for every accepted input: each module
77//! has exactly one encoding.
78//! - [`encode`] and [`disassemble`] are deterministic.
79//! - A function produced by [`ModuleBuilder::add_function`] has no unresolved
80//! or out-of-range branch target.
81//!
82//! What this release does **not** do: verify a module's meaning (index
83//! ranges, register types, frame shapes). That is the v0.5 verifier, whose
84//! rules are specified in `specs/LSB.md` §8. Until then a decoded module is
85//! well-formed but unverified.
86
87#![cfg_attr(not(feature = "std"), no_std)]
88#![cfg_attr(docsrs, feature(doc_cfg))]
89#![forbid(unsafe_code)]
90#![deny(missing_docs)]
91#![deny(unused_must_use)]
92#![deny(unused_results)]
93#![deny(clippy::unwrap_used)]
94#![deny(clippy::expect_used)]
95#![deny(clippy::todo)]
96#![deny(clippy::unimplemented)]
97#![deny(clippy::print_stdout)]
98#![deny(clippy::print_stderr)]
99#![deny(clippy::dbg_macro)]
100
101extern crate alloc;
102
103#[macro_use]
104mod macros;
105
106mod builder;
107mod call;
108mod decode;
109mod disasm;
110mod encode;
111mod ids;
112mod inst;
113mod module;
114mod policy;
115mod types;
116
117use alloc::string::{String, ToString};
118use alloc::vec::Vec;
119
120pub use builder::{BuildError, FunctionBuilder, Label, ModuleBuilder};
121pub use call::{
122 ArgItem, ArgKind, BindError, Binding, Bound, CallShape, Param, ParamError, ParamKind,
123 ParamList, ShapeError,
124};
125pub use decode::{DecodeError, DecodeErrorKind, Limit, Limits};
126pub use ids::{
127 ConstId, FieldIdx, FuncId, GlobalId, ImportId, NameRef, Reg, ShapeId, StrId, TableId, Target,
128 TypeId, TypeRef, UpvalIdx,
129};
130pub use inst::{FieldKind, FieldSpec, Inst, InstError, Opcode, Slot};
131pub use module::{
132 Callee, Const, ErrorKind, Export, ExportItem, Function, Global, Handler, Hook, HookBinding,
133 Import, JumpTable, LineRow, LocalVar, Module,
134};
135pub use policy::{
136 DivZero, FloatConv, FloatToInt, FloatTy, IntConv, IntOp, IntPair, IntTy, Overflow, Policy,
137 Shift,
138};
139pub use types::{CoroState, Field, FuncType, Kind, Method, Prim, StructDef, TypeDef, ValType};
140
141/// The four bytes every encoded module starts with: `LSB\0`.
142///
143/// # Examples
144///
145/// ```
146/// use bytecode_lang::{encode, ModuleBuilder, MAGIC};
147///
148/// let bytes = encode(&ModuleBuilder::new().finish().unwrap());
149/// assert_eq!(&bytes[..4], &MAGIC);
150/// ```
151pub const MAGIC: [u8; 4] = *b"LSB\0";
152
153/// The format version this crate writes and the only one it reads.
154///
155/// The version changes whenever the encoding or any instruction's meaning
156/// changes; [`decode`] refuses every other version rather than guess.
157/// Version 2 (bytecode-lang 0.3) added parameter lists and call shapes to
158/// the function and import records, repacked the policy bytes for
159/// `shift = saturate`, and added the reference, separation, `pow`, `abs`,
160/// shaped-call, and `raise` instructions (`specs/LSB.md` §7.4).
161///
162/// # Examples
163///
164/// ```
165/// use bytecode_lang::{encode, ModuleBuilder, FORMAT_VERSION};
166///
167/// let bytes = encode(&ModuleBuilder::new().finish().unwrap());
168/// assert_eq!(bytes[4..8], FORMAT_VERSION.to_le_bytes());
169/// assert_eq!(FORMAT_VERSION, 2);
170/// ```
171pub const FORMAT_VERSION: u32 = 2;
172
173/// Encodes a module into its canonical bytes.
174///
175/// Infallible and deterministic: a [`Module`] can only be made by
176/// [`ModuleBuilder::finish`] or [`decode`], both of which guarantee every
177/// section fits its 32-bit length.
178///
179/// # Examples
180///
181/// ```
182/// use bytecode_lang::{encode, ModuleBuilder};
183///
184/// let module = ModuleBuilder::new().finish().unwrap();
185/// let bytes = encode(&module);
186/// assert_eq!(bytes, encode(&module));
187/// // A 12-byte header, ten 8-byte section headers, and the empty sections'
188/// // counts and option flags.
189/// assert_eq!(bytes.len(), 130);
190/// ```
191#[must_use]
192pub fn encode(module: &Module) -> Vec<u8> {
193 encode::encode(module)
194}
195
196/// Decodes a module with the default [`Limits`].
197///
198/// # Errors
199///
200/// A [`DecodeError`] with the byte offset and the reason when the input is
201/// not a well-formed module of this format version.
202///
203/// # Examples
204///
205/// ```
206/// use bytecode_lang::{decode, encode, DecodeErrorKind, ModuleBuilder};
207///
208/// let module = ModuleBuilder::new().finish().unwrap();
209/// let bytes = encode(&module);
210/// assert_eq!(decode(&bytes).unwrap(), module);
211///
212/// let truncated = &bytes[..bytes.len() - 1];
213/// assert_eq!(decode(truncated).unwrap_err().kind(), &DecodeErrorKind::UnexpectedEnd);
214/// ```
215pub fn decode(bytes: &[u8]) -> Result<Module, DecodeError> {
216 decode::decode(bytes, &Limits::default())
217}
218
219/// Decodes a module with explicit budgets.
220///
221/// # Errors
222///
223/// As [`decode`], plus [`DecodeErrorKind::LimitExceeded`] when the input
224/// exceeds one of `limits`.
225///
226/// # Examples
227///
228/// ```
229/// use bytecode_lang::{decode_with, encode, DecodeErrorKind, Limit, Limits, ModuleBuilder};
230///
231/// let bytes = encode(&ModuleBuilder::new().finish().unwrap());
232/// let mut tight = Limits::default();
233/// tight.max_bytes = 16;
234/// assert_eq!(
235/// decode_with(&bytes, &tight).unwrap_err().kind(),
236/// &DecodeErrorKind::LimitExceeded(Limit::Bytes),
237/// );
238/// ```
239pub fn decode_with(bytes: &[u8], limits: &Limits) -> Result<Module, DecodeError> {
240 decode::decode(bytes, limits)
241}
242
243/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md` as
244/// part of `cargo test`, so the published examples cannot drift from the API.
245///
246/// Present only while collecting doctests (`#[cfg(doctest)]`); it is not part of
247/// the public surface and does not appear in the built library or its docs.
248#[cfg(doctest)]
249#[doc = include_str!("../README.md")]
250#[doc = include_str!("../docs/API.md")]
251pub struct MarkdownDocTests;
252
253/// The module's disassembly: a deterministic text listing of every table
254/// and every function, with branch targets as labels and names resolved in
255/// comments.
256///
257/// Never panics, including on unverified modules with out-of-range
258/// references (they print as `<invalid>`).
259///
260/// # Examples
261///
262/// ```
263/// use bytecode_lang::{disassemble, ModuleBuilder};
264///
265/// let mut m = ModuleBuilder::new();
266/// let mut f = m.function("main", &[], &[]);
267/// f.ret_void();
268/// m.add_function(f).unwrap();
269/// let text = disassemble(&m.finish().unwrap());
270/// assert!(text.starts_with("lsb 2\n"));
271/// assert!(text.contains("func f0 s0 \"main\" : t0"));
272/// assert!(text.contains(" 0000 ret_void"));
273/// ```
274#[must_use]
275pub fn disassemble(module: &Module) -> String {
276 module.to_string()
277}