Skip to main content

vyre_reference/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(
3    clippy::only_used_in_recursion,
4    clippy::comparison_chain,
5    clippy::ptr_arg
6)]
7//! Pure Rust reference interpreter for vyre IR programs.
8//!
9//! This module is the executable specification for IR semantics. It is
10//! intentionally slow and direct: every current IR expression and node variant
11//! has a named evaluator function.
12
13extern crate vyre_foundation as vyre;
14
15/// Dual-reference trait and registry types.
16pub mod dual;
17/// Canonical dual implementations and reference evaluators.
18pub mod dual_impls;
19/// Runtime value representation for interpreter inputs and outputs.
20pub mod value;
21
22/// Atomic operation reference implementations.
23pub mod atomics;
24/// CPU operation traits used by concrete reference implementations.
25pub mod cpu_op;
26/// Registry-driven dispatch entry point (B-B4).
27///
28/// Routes an op id through the global `DialectRegistry` and invokes
29/// the registered `cpu_ref` function. Complements the execution-tree
30/// evaluators by giving external dialect crates a zero-patch path to run on
31/// the reference interpreter.
32pub mod dialect_dispatch;
33/// Canonical reference execution tree.
34pub mod execution;
35/// Flat byte adapter used by [`crate::cpu_op::CpuOp`].
36pub mod flat_cpu;
37/// IEEE 754 strict floating-point utilities.
38pub mod ieee754;
39/// Subgroup simulator for lane-collective Cat-C ops.
40pub mod subgroup;
41/// Workgroup simulation: invocation IDs, shared memory.
42pub mod workgroup;
43
44mod oob;
45mod ops;
46
47/// A tally of out-of-bounds accesses the interpreter silently absorbed during a
48/// tracked run, surfaces the masking that hides GPU/CPU parity hazards. See
49/// [`reference_eval_oob_report`].
50pub use oob::OobReport;
51
52/// Test-only entry point that runs the hashmap interpreter over a Program.
53#[cfg(test)]
54pub use execution::eval_hashmap_reference;
55/// Count arithmetic IR ops the reference interpreter executes in a scope (roofline /
56/// complexity analysis) (a backend-agnostic dynamic operation count).
57pub use execution::op_count::count_ops;
58/// The interpreter's output ABI: [`is_reference_output`] is the single predicate
59/// `reference_eval` uses to select the buffers it returns, and [`output_index`] locates
60/// a named output by that predicate, so test harnesses derive the output ordering from
61/// the interpreter instead of re-deriving (and drifting from) it.
62pub use execution::{is_reference_output, output_index};
63/// Execute a vyre Program on the pure Rust reference interpreter.
64pub use execution::{
65    reference_eval, reference_eval_lane_reversed, reference_eval_oob_report,
66    reference_eval_with_dispatch, reference_eval_with_dispatch_oob_report, run_arena_reference,
67    run_arena_reference_with_dispatch, run_storage_graph,
68};
69
70/// Resolve an operation ID to its two independently-written references.
71///
72/// # Examples
73///
74/// ```
75/// use vyre_reference::{dual_impls, resolve_dual};
76///
77/// let (reference_a, reference_b) =
78///     resolve_dual(dual_impls::bitwise::xor::OP_ID).expect("Fix: xor dual refs must be registered; restore this invariant before continuing.");
79///
80/// let input = [0b1010_1010_u8, 0b0101_0101];
81/// assert_eq!(reference_a(&input), reference_b(&input));
82/// ```
83pub fn resolve_dual(op_id: &str) -> Option<(dual::ReferenceFn, dual::ReferenceFn)> {
84    match op_id {
85        dual_impls::arith::add::OP_ID => Some((
86            dual_impls::arith::add::reference_a::reference,
87            dual_impls::arith::add::reference_b::reference,
88        )),
89        dual_impls::arith::mul::OP_ID => Some((
90            dual_impls::arith::mul::reference_a::reference,
91            dual_impls::arith::mul::reference_b::reference,
92        )),
93        dual_impls::bitwise::xor::OP_ID => Some((
94            dual_impls::bitwise::xor::reference_a::reference,
95            dual_impls::bitwise::xor::reference_b::reference,
96        )),
97        dual_impls::bitwise::and::OP_ID => Some((
98            dual_impls::bitwise::and::reference_a::reference,
99            dual_impls::bitwise::and::reference_b::reference,
100        )),
101        dual_impls::bitwise::or::OP_ID => Some((
102            dual_impls::bitwise::or::reference_a::reference,
103            dual_impls::bitwise::or::reference_b::reference,
104        )),
105        dual_impls::bitwise::not::OP_ID => Some((
106            dual_impls::bitwise::not::reference_a::reference,
107            dual_impls::bitwise::not::reference_b::reference,
108        )),
109        dual_impls::bitwise::shift_left::OP_ID => Some((
110            dual_impls::bitwise::shift_left::reference_a::reference,
111            dual_impls::bitwise::shift_left::reference_b::reference,
112        )),
113        dual_impls::bitwise::shift_right::OP_ID => Some((
114            dual_impls::bitwise::shift_right::reference_a::reference,
115            dual_impls::bitwise::shift_right::reference_b::reference,
116        )),
117        dual_impls::bitwise::popcount::OP_ID => Some((
118            dual_impls::bitwise::popcount::reference_a::reference,
119            dual_impls::bitwise::popcount::reference_b::reference,
120        )),
121        dual_impls::bitwise::clz::OP_ID => Some((
122            dual_impls::bitwise::clz::reference_a::reference,
123            dual_impls::bitwise::clz::reference_b::reference,
124        )),
125        dual_impls::compare::eq::OP_ID => Some((
126            dual_impls::compare::eq::reference_a::reference,
127            dual_impls::compare::eq::reference_b::reference,
128        )),
129        dual_impls::compare::lt::OP_ID => Some((
130            dual_impls::compare::lt::reference_a::reference,
131            dual_impls::compare::lt::reference_b::reference,
132        )),
133        _ => None,
134    }
135}
136
137/// Return the complete list of operation IDs that have dual references registered.
138///
139/// This is the canonical enumeration used by the differential fuzzing gate.
140/// Every new dual-reference pair MUST add its OP_ID here.
141pub fn dual_op_ids() -> &'static [&'static str] {
142    &[
143        dual_impls::arith::add::OP_ID,
144        dual_impls::arith::mul::OP_ID,
145        dual_impls::bitwise::xor::OP_ID,
146        dual_impls::bitwise::and::OP_ID,
147        dual_impls::bitwise::or::OP_ID,
148        dual_impls::bitwise::not::OP_ID,
149        dual_impls::bitwise::shift_left::OP_ID,
150        dual_impls::bitwise::shift_right::OP_ID,
151        dual_impls::bitwise::popcount::OP_ID,
152        dual_impls::bitwise::clz::OP_ID,
153        dual_impls::compare::eq::OP_ID,
154        dual_impls::compare::lt::OP_ID,
155    ]
156}
157
158/// The architecture of the `OpEntry` registry.
159///
160/// We are forced to split the global primitive registries into three separate
161/// buckets (Unary, Binary, Variadic) instead of a single unified registry.
162///
163/// This split is required because of Rust's trait object lifetime limits.
164/// When storing function pointers that take references (e.g., `&'a Node<'a>`),
165/// higher-ranked trait bounds (HRTB, `for<'a>`) fail to unify on function
166/// pointers with heterogeneous arities. A single registry `fn(&[Node])` slice
167/// signature would force heap allocation for binary/unary nodes to fit the slice,
168/// destroying the zero-allocation invariant of the reference interpreter.
169///
170/// Thus, we split by arity to allow zero-cost static dispatch.
171pub mod registry_architecture {}