Skip to main content

rssn_advanced/
lib.rs

1//! # rssn-advanced
2//!
3//! **rssn-advanced** is the symbolic computation engine of the
4//! [rssn](https://github.com/Apich-Organization/rssn) project.  It provides a
5//! hash-consed expression DAG, a [Cranelift](https://cranelift.dev/)-backed JIT
6//! compiler, heuristic and e-graph simplification, multi-architecture inline-asm
7//! presets, and a flat `extern "C"` API for embedding in C/C++ and other languages.
8//!
9//! ---
10//!
11//! ## Architecture
12//!
13//! | Module | Role |
14//! |--------|------|
15//! | [`dag`] | Hash-consed expression DAG — the canonical, deduplicated store for all symbolic nodes |
16//! | [`ast`] | Lightweight local tree projection of a DAG subgraph via relative `i32` pointers |
17//! | [`parser`] | `nom`-based infix parser: `"x^2 + 2*x + 1"` → DAG root |
18//! | [`jit`] *(feature: `cranelift-jit`)* | Cranelift JIT; emits scalar `f64` closures and 2-row ILP batch functions |
19//! | [`heuristic`] | Configurable greedy/beam simplifier with a pluggable [`heuristic::rule_registry::RuleRegistry`] |
20//! | [`egraph`] | Lightweight equality saturation over the DAG (no `egg` dependency) |
21//! | [`custom`] | Unified custom-operator system — one [`custom::descriptor::CustomOpDescriptor`] wires into JIT + simplifier + e-graph |
22//! | [`simd`] | Slice-level batch arithmetic using the inline-asm presets |
23//! | [`asm_presets`] | Hand-written `f64×2` / `f64×4` kernels for `x86_64` (SSE2/AVX2/AES-NI), `AArch64` (NEON/crypto), riscv64 (RVV/Zkn) |
24//! | [`ffi`] | Flat `extern "C"` surface generated by `cbindgen`; includes a fiber-backed async bridge |
25//! | [`parallel`] | Fiber-based parallel simplification via the `dtact` runtime |
26//! | [`storage`] | Disk-backed DAG spillover and a frequency-based hot-node cache |
27//! | [`error`] | Cold-path error types and the `rssn_error!` macro |
28//!
29//! ---
30//!
31//! ## Quick start
32//!
33//! ### Parse and evaluate
34//!
35//! ```rust
36//! use rssn_advanced::dag::builder::DagBuilder;
37//! use rssn_advanced::parser::expr::parse_expression;
38//!
39//! let mut builder = DagBuilder::new();
40//! // parse_expression(input, &mut builder) -> Result<DagNodeId, ParseError>
41//! let root = parse_expression("x^2 + 2*x + 1", &mut builder).unwrap();
42//! let _ = root;
43//! ```
44//!
45//! ### JIT-compile and bulk-evaluate
46//!
47//! ```rust
48//! # // cfg guard: skip when cranelift-jit feature is absent
49//! # #[cfg(not(feature = "cranelift-jit"))] fn main() {}
50//! # #[cfg(feature = "cranelift-jit")] fn main() {
51//! use rssn_advanced::dag::builder::DagBuilder;
52//! use rssn_advanced::parser::expr::parse_expression;
53//! use rssn_advanced::ast::convert::dag_to_ast;
54//! use rssn_advanced::jit::compiler::JitCompiler;
55//!
56//! let mut builder = DagBuilder::new();
57//! let root = parse_expression("x^2 + 2*x + 1", &mut builder).unwrap();
58//!
59//! let mut compiler = JitCompiler::try_new().unwrap();
60//! let ast = dag_to_ast(builder.arena(), root);
61//! let f   = compiler.compile(&ast).unwrap();
62//!
63//! // CompiledExprFn = extern "C" fn(*const f64) -> f64
64//! assert_eq!(f([3.0_f64].as_ptr()), 16.0); // (3+1)^2
65//!
66//! // 2-row ILP batch path; returns None if expression is not vectorizable
67//! let _batch = compiler.compile_batch_f64x2(&ast).unwrap();
68//! # }
69//! ```
70//!
71//! ### Register a custom operator
72//!
73//! ```rust
74//! # // cfg guard: skip when cranelift-jit feature is absent
75//! # #[cfg(not(feature = "cranelift-jit"))] fn main() {}
76//! # #[cfg(feature = "cranelift-jit")] fn main() {
77//! use std::sync::Arc;
78//! use rssn_advanced::dag::builder::DagBuilder;
79//! use rssn_advanced::custom::descriptor::{CustomOpDescriptor, CustomOpRegistry, EvalFn};
80//! use rssn_advanced::egraph::egraph::{EGraph, EGraphConfig};
81//! use rssn_advanced::jit::compiler::JitCompiler;
82//!
83//! extern "C" fn my_relu(x: f64) -> f64 { x.max(0.0) }
84//!
85//! let mut builder = DagBuilder::new();
86//! // intern_function returns the FnId used to identify this operator
87//! let fn_id = builder.intern_function("relu");
88//!
89//! let desc = CustomOpDescriptor::builder(fn_id, "relu", EvalFn::Arity1(my_relu))
90//!     .vectorizable()   // safe to duplicate in the 2-row ILP batch path
91//!     .cost(1.0)
92//!     .build();
93//!
94//! let mut reg = CustomOpRegistry::new();
95//! reg.register(desc).unwrap();
96//! let reg = Arc::new(reg);
97//!
98//! // Wire into the JIT (registers the eval_fn pointer)
99//! let mut compiler = JitCompiler::try_new().unwrap();
100//! reg.apply_to_jit(&mut compiler);
101//!
102//! // Wire into the heuristic simplifier
103//! let _rule_reg = reg.build_rule_registry();
104//!
105//! // Wire into the e-graph saturation engine
106//! {
107//!     let mut egraph = EGraph::new(&mut builder, EGraphConfig::default());
108//!     reg.apply_to_egraph(&mut egraph);
109//! }
110//! # }
111//! ```
112//!
113//! ---
114//!
115//! ## Performance
116//!
117//! Benchmark: bulk evaluation of N = 1,000,000 rows, best of 5 runs.
118//! Hardware: Dell Latitude 5400, Intel i7-8665U @ 1.90 GHz (laptop-class, 16 MB L3),
119//! Fedora Linux 44, kernel 6.19.  Compared against hand-optimised `NumPy` 1.x
120//! (BLAS-linked, SIMD-enabled).
121//!
122//! | Expression | JIT bulk | JIT batch | `NumPy` | Speedup (bulk / batch) |
123//! |------------|--------:|----------:|------:|-----------------------:|
124//! | `x + y + 10.0` (trivial baseline) | 1.87 ns | 1.13 ns | 2.82 ns | **1.5× / 2.5×** |
125//! | `(x-y)^4` — degree-4 polynomial   | 2.73 ns | 1.28 ns | 19.21 ns | **7× / 15×** |
126//! | cubic surface (10 terms, 3 vars)  | 3.73 ns | 1.77 ns | 75.75 ns | **20× / 43×** |
127//! | rational expression w/ CSE        | 2.53 ns | 1.27 ns | 15.91 ns | **6× / 13×** |
128//!
129//! **Why the gap grows with complexity:** `NumPy` allocates one `f64[N]` scratch array
130//! per arithmetic operation.  A 10-term expression at N = 10⁶ creates ~200 MB of
131//! temporaries that thrash L3 cache.  The JIT keeps every intermediate value in a
132//! CPU register across the full expression, paying exactly one memory round-trip
133//! per input column.
134//!
135//! **Honest caveats:**
136//! - Numbers are from a single laptop; server CPUs with larger L3 caches will show a
137//!   smaller gap for simple expressions.
138//! - The batch path unrolls 2 rows for ILP but does **not** emit wide-vector AVX/AVX-512
139//!   instructions; the [`asm_presets`] / [`simd`] paths are faster for fixed-width kernels.
140//! - Cranelift's code quality is good but not hand-tuned; `gcc -O3` / LLVM can sometimes
141//!   produce tighter loops for simple expressions.
142//!
143//! ---
144//!
145//! ## Feature flags
146//!
147//! | Flag | Default | Effect |
148//! |------|:-------:|--------|
149//! | `cranelift-jit` | **on** | Enables the [`jit`] module, JIT compilation, and the batch-evaluate path |
150//!
151//! Disable with `--no-default-features` for embedded or size-constrained targets.
152//! The parser, DAG, heuristic simplifier, e-graph, and SIMD presets are all
153//! available without the JIT feature.
154//!
155//! ---
156//!
157//! ## Known limitations
158//!
159//! - **Parser scope:** handles `+`, `-`, `*`, `/`, `^`, `%`, unary negation, and
160//!   registered named functions.  Transcendentals (`sin`, `exp`, …) must be
161//!   registered as custom operators.
162//! - **Single-threaded JIT context:** compilation requests from multiple threads
163//!   serialise on a global `Mutex`.
164//! - **No GPU or BLAS integration.**
165//! - **`asm_presets` on Windows:** some NEON / RVV paths are x86/AArch64/riscv64
166//!   specific; the scalar fallback is always available.
167//! - **e-graph extractor is greedy:** the current cost-minimising extractor uses a
168//!   greedy bottom-up pass; optimal extraction is NP-hard and not yet implemented.
169#![doc(
170    html_logo_url = "https://raw.githubusercontent.com/Apich-Organization/rssn/refs/heads/dev/doc/logo.png"
171)]
172#![doc(
173    html_favicon_url = "https://raw.githubusercontent.com/Apich-Organization/rssn/refs/heads/dev/doc/favicon.ico"
174)]
175// -------------------------------------------------------------------------
176// Rust Lint Configuration: rssn-advanced
177// -------------------------------------------------------------------------
178
179// -------------------------------------------------------------------------
180// LEVEL 1: CRITICAL ERRORS (Deny)
181// -------------------------------------------------------------------------
182#![deny(
183    // Rust Compiler Errors
184    dead_code,
185    unreachable_code,
186    improper_ctypes_definitions,
187    future_incompatible,
188    nonstandard_style,
189    rust_2018_idioms,
190    clippy::perf,
191    clippy::correctness,
192    clippy::suspicious,
193    clippy::unwrap_used,
194    clippy::expect_used,
195    clippy::indexing_slicing,
196    clippy::arithmetic_side_effects,
197    clippy::missing_safety_doc,
198    clippy::same_item_push,
199    clippy::implicit_clone,
200    clippy::all,
201    clippy::pedantic,
202    warnings,
203    missing_docs,
204    clippy::nursery,
205    clippy::single_call_fn,
206)]
207// -------------------------------------------------------------------------
208// LEVEL 2: STYLE WARNINGS (Warn)
209// -------------------------------------------------------------------------
210#![warn(
211    clippy::dbg_macro,
212    warnings,
213    clippy::todo,
214    clippy::unnecessary_safety_comment
215)]
216// -------------------------------------------------------------------------
217// LEVEL 3: ALLOW/IGNORABLE (Allow)
218// -------------------------------------------------------------------------
219#![allow(
220    unsafe_code,
221    clippy::inline_always,
222    clippy::restriction,
223    clippy::cast_possible_truncation,
224    clippy::cast_sign_loss,
225    clippy::items_after_statements,
226    clippy::too_long_first_doc_paragraph,
227    clippy::non_send_fields_in_send_ty,
228    unused_doc_comments,
229    clippy::empty_line_after_outer_attr,
230    clippy::empty_line_after_doc_comments
231)]
232
233// =========================================================================
234// Module Declarations
235// =========================================================================
236
237/// Inline-assembly preset suite (AVX2 / AES-NI / scalar fallback).
238///
239/// Each preset is a 4-lane `f64` kernel emitted via `core::arch::asm!`
240/// — no reliance on auto-vectorization. Used by both `simd` (slice
241/// wrappers) and indirectly by `jit` (peephole patterns). Lives at the
242/// crate root so neither subsystem has to feature-gate the other.
243pub mod asm_presets;
244
245/// Cold-path error infrastructure.
246///
247/// Hosts the `rssn_error!` macro and the module-level error enums.
248/// Replaces the previous ad-hoc `unwrap()` / `expect()` / `assert_eq!`
249/// pattern with `#[cold] #[inline(never)]` constructors so that error
250/// handling stays off the hot path.
251pub mod error;
252
253/// Zero-copy borrowed containers and `bincode-next` `BorrowDecode` glue.
254///
255/// `BorrowedSlice` / `BorrowedArena` decode by `take_bytes` directly off
256/// the input buffer, and `MmapBuffer` provides file-backed storage with
257/// 8-byte aligned bytes for safe reinterpretation as `&[T: Pod]`.
258pub mod zerocopy;
259
260/// Fiber-based task runtime built on `dtact`.
261///
262/// Replaces `std::thread::spawn` with lightweight fibers throughout the
263/// crate. `parallel_for_each` is the workhorse used by `parallel::solver`
264/// and `ffi::async_bridge`.
265pub mod runtime;
266
267/// Allocator-light shared utilities (worklist traversals, helpers).
268pub mod util;
269
270/// Global DAG (Directed Acyclic Graph) storage for symbolic expressions.
271///
272/// Provides hash-consed, structurally-shared storage for all symbol nodes.
273/// The DAG serves as the canonical representation — all expression data
274/// ultimately lives here, deduplicated via structural hashing.
275pub mod dag;
276
277/// Local AST (Abstract Syntax Tree) projection for computation.
278///
279/// Projects a subgraph of the global DAG into a stack-local tree using
280/// relative pointers (`i32` / `i64`). This provides an algorithm-friendly
281/// tree view without duplicating the underlying metadata.
282pub mod ast;
283
284/// Symbolic expression parser.
285///
286/// Parses mathematical expressions (e.g. `"x^2 + 2*x + 1"`) into the
287/// global DAG using `nom`-based combinators with precedence climbing.
288pub mod parser;
289
290/// JIT compilation pipeline for symbolic derivation rules.
291///
292/// Compiles algebraic rewrite rules (add, mul, div, custom) into native
293/// machine code via Cranelift. Gated behind the `jit` feature (alias
294/// `cranelift-jit` kept for backward compatibility).
295#[cfg(feature = "jit")]
296pub mod jit;
297
298/// Parallel computation engine.
299///
300/// Exploits commutativity to split expressions into independent chunks
301/// for async parallel simplification, with staged global merging.
302pub mod parallel;
303
304/// Streaming storage and dynamic caching.
305///
306/// Provides disk-backed spillover for large DAGs and a dynamic hotspot
307/// table that tracks intermediate result frequency for auto-eviction.
308pub mod storage;
309
310/// Heuristic search toolbox for NP-hard pattern matching.
311///
312/// A configurable "knob-based" engine that allows controlled approximate
313/// simplification when exact methods hit symbol explosion.
314pub mod heuristic;
315
316/// Lightweight E-graph for equality saturation.
317///
318/// Implements equality saturation over the hash-consed DAG without importing
319/// the heavy `egg` crate. Uses a path-compressed union-find directly over
320/// [`dag::node::DagNodeId`] values, and extracts the minimum-cost
321/// representative after each saturation run.
322pub mod egraph;
323
324/// SIMD-optimized preset function library.
325///
326/// Hardware-accelerated batch operations (arithmetic, hashing) with
327/// runtime feature detection and scalar fallback.
328pub mod simd;
329
330/// Unified custom-operator extension system.
331///
332/// A single [`custom::descriptor::CustomOpDescriptor`] bundles every
333/// pipeline-facing property of a user-defined operator: JIT eval function,
334/// batch-vectorisability flag, heuristic simplification rules, and e-graph
335/// rewrite rules.  Register descriptors into a
336/// [`custom::descriptor::CustomOpRegistry`], then call the three integration
337/// methods to wire the operator into all pipeline stages simultaneously:
338///
339/// - `registry.apply_to_jit(&mut compiler)` — feeds `eval_fn` pointers to
340///   the JIT and enables the 2-row ILP batch path for vectorizable operators.
341/// - `registry.build_rule_registry()` — produces a [`heuristic::rule_registry::RuleRegistry`]
342///   from all attached [`custom::descriptor::SimplifyRule`]s.
343/// - `registry.apply_to_egraph(&mut egraph)` — injects all
344///   [`custom::descriptor::EGraphRule`]s into an [`egraph::egraph::EGraph`].
345///
346/// See the [crate-level "Register a custom operator" example](crate) for a
347/// runnable end-to-end demonstration.
348///
349/// C/C++ callers use the `rssn_custom_op_*` family in [`ffi::c_api`].
350pub mod custom;
351
352/// C/C++ Foreign Function Interface.
353///
354/// Exposes a flat, `extern "C"` API surface via `cbindgen`-compatible
355/// types and opaque handles. Includes an async bridge for multi-language
356/// integration.
357pub mod ffi;
358
359#[cfg_attr(miri, ignore)]
360mod readme {
361    #![doc = include_str!("../README.md")]
362}