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 {}