1pub(crate) mod call;
8pub mod expr;
9pub(crate) mod expr_cast;
10pub(crate) mod hashmap;
11pub mod node;
12pub(crate) mod node_tree;
13pub mod op_count;
15pub mod sequential;
16pub(crate) mod typed_ops;
17
18use std::borrow::Cow;
19
20use rustc_hash::FxHashMap;
21use vyre::ir::{InterpCtx, Node, NodeId, NodeStorage, Program, Value as IrValue};
22
23use crate::value::Value;
24
25pub(crate) fn program_for_interpreter(program: &Program) -> Result<Cow<'_, Program>, vyre::Error> {
33 let normalized = if let Some(message) = program.top_level_region_violation() {
34 if program.entry().is_empty() {
35 return Err(vyre::Error::interp(format!(
36 "reference interpreter requires a top-level Region-wrapped Program: {message}"
37 )));
38 }
39 if matches!(program.entry().first(), Some(Node::Store { .. })) {
40 return Err(vyre::Error::interp(format!(
41 "reference interpreter requires a top-level Region-wrapped Program: {message}"
42 )));
43 }
44 Cow::Owned(program.clone().reconcile_runnable_top_level())
45 } else {
46 Cow::Borrowed(program)
47 };
48 match vyre_foundation::transform::collectives::lower_single_rank_collectives(
49 normalized.as_ref(),
50 ) {
51 Ok(Some(lowered)) => Ok(Cow::Owned(lowered)),
52 Ok(None) => Ok(normalized),
53 Err(error) => Err(vyre::Error::interp(error.to_string())),
54 }
55}
56
57pub use hashmap::{is_reference_output, output_index};
62
63pub fn reference_eval(program: &Program, inputs: &[Value]) -> Result<Vec<Value>, vyre::Error> {
69 run_arena_reference(program, inputs)
70}
71
72pub fn reference_eval_oob_report(
91 program: &Program,
92 inputs: &[Value],
93) -> Result<(Vec<Value>, crate::oob::OobReport), vyre::Error> {
94 crate::oob::reset_oob_report();
95 let outputs = reference_eval(program, inputs)?;
96 Ok((outputs, crate::oob::oob_report()))
97}
98
99pub fn reference_eval_with_dispatch_oob_report(
111 program: &Program,
112 inputs: &[Value],
113 min_dispatch_elements: u32,
114) -> Result<(Vec<Value>, crate::oob::OobReport), vyre::Error> {
115 crate::oob::reset_oob_report();
116 let outputs = reference_eval_with_dispatch(program, inputs, min_dispatch_elements)?;
117 Ok((outputs, crate::oob::oob_report()))
118}
119
120pub fn reference_eval_with_dispatch(
134 program: &Program,
135 inputs: &[Value],
136 min_dispatch_elements: u32,
137) -> Result<Vec<Value>, vyre::Error> {
138 run_arena_reference_with_dispatch(program, inputs, min_dispatch_elements)
139}
140
141pub fn run_arena_reference(program: &Program, inputs: &[Value]) -> Result<Vec<Value>, vyre::Error> {
143 run_arena_reference_with_dispatch(program, inputs, 0)
144}
145
146pub fn run_arena_reference_with_dispatch(
152 program: &Program,
153 inputs: &[Value],
154 min_dispatch_elements: u32,
155) -> Result<Vec<Value>, vyre::Error> {
156 let program = program_for_interpreter(program)?;
157 hashmap::run_hashmap_reference(
158 &program,
159 inputs,
160 min_dispatch_elements,
161 hashmap::LaneOrder::Forward,
162 )
163}
164
165pub fn reference_eval_lane_reversed(
179 program: &Program,
180 inputs: &[Value],
181) -> Result<Vec<Value>, vyre::Error> {
182 let program = program_for_interpreter(program)?;
183 hashmap::run_hashmap_reference(&program, inputs, 0, hashmap::LaneOrder::Reversed)
184}
185
186#[cfg(test)]
188pub fn eval_hashmap_reference(
189 program: &Program,
190 inputs: &[Value],
191) -> Result<Vec<Value>, vyre::Error> {
192 run_arena_reference(program, inputs)
193}
194
195pub fn run_storage_graph(
197 nodes: &[(NodeId, NodeStorage)],
198 outputs: &[NodeId],
199) -> Result<Vec<IrValue>, vyre::Error> {
200 let mut graph = FxHashMap::with_capacity_and_hasher(nodes.len(), Default::default());
201 for (id, node) in nodes {
202 if graph.insert(*id, node).is_some() {
203 return Err(duplicate_node_error(*id));
204 }
205 }
206 let mut ctx = InterpCtx::default();
207 let mut states = FxHashMap::with_capacity_and_hasher(graph.len(), Default::default());
208
209 for output in outputs {
210 eval_storage_node(*output, &graph, &mut ctx, &mut states)?;
211 }
212
213 outputs
214 .iter()
215 .map(|id| ctx.get(*id).map_err(interp_error))
216 .collect()
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220enum VisitState {
221 Visiting,
222 Done,
223}
224
225fn eval_storage_node(
226 id: NodeId,
227 graph: &FxHashMap<NodeId, &NodeStorage>,
228 ctx: &mut InterpCtx,
229 states: &mut FxHashMap<NodeId, VisitState>,
230) -> Result<(), vyre::Error> {
231 match states.get(&id).copied() {
232 Some(VisitState::Done) => return Ok(()),
233 Some(VisitState::Visiting) => return Err(cycle_error(id)),
234 None => {}
235 }
236
237 let node = *graph.get(&id).ok_or_else(|| missing_node_error(id))?;
238 states.insert(id, VisitState::Visiting);
239 let inputs = node.input_ids();
240 for input in &inputs {
241 eval_storage_node(*input, graph, ctx, states)?;
242 }
243 ctx.set_operands(inputs);
244 let value = node.interpret(ctx).map_err(interp_error)?;
245 ctx.set(id, value);
246 states.insert(id, VisitState::Done);
247 Ok(())
248}
249
250fn interp_error(error: vyre::ir::EvalError) -> vyre::Error {
251 vyre::Error::interp(error.to_string())
252}
253
254fn missing_node_error(id: NodeId) -> vyre::Error {
255 vyre::Error::interp(format!(
256 "graph references missing node {}. Fix: include every dependency in the interpreter input graph.",
257 id.0
258 ))
259}
260
261fn cycle_error(id: NodeId) -> vyre::Error {
262 vyre::Error::interp(format!(
263 "graph contains a dependency cycle at node {}. Fix: submit an acyclic dataflow graph.",
264 id.0
265 ))
266}
267
268fn duplicate_node_error(id: NodeId) -> vyre::Error {
269 vyre::Error::interp(format!(
270 "graph contains duplicate node {}. Fix: submit exactly one storage record for each NodeId before reference execution.",
271 id.0
272 ))
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use vyre::ir::{BinOp, BufferAccess, BufferDecl, DataType, Expr, Node, NodeStorage};
279
280 #[test]
281 fn reference_eval_dispatches_singleton_atomic_flags_across_dynamic_byte_input() {
282 let program = Program::wrapped(
283 vec![
284 BufferDecl::storage("bytes_in", 0, BufferAccess::ReadOnly, DataType::U8)
285 .with_count(0),
286 BufferDecl::storage("flag", 1, BufferAccess::ReadWrite, DataType::U32)
287 .with_count(1),
288 ],
289 [256, 1, 1],
290 vec![
291 Node::let_bind("i", Expr::InvocationId { axis: 0 }),
292 Node::if_then(
293 Expr::lt(Expr::var("i"), Expr::buf_len("bytes_in")),
294 vec![Node::if_then(
295 Expr::ne(
296 Expr::cast(DataType::U32, Expr::load("bytes_in", Expr::var("i"))),
297 Expr::u32(0),
298 ),
299 vec![Node::let_bind(
300 "flag_old",
301 Expr::atomic_or("flag", Expr::u32(0), Expr::u32(1)),
302 )],
303 )],
304 ),
305 ],
306 );
307 let mut bytes = vec![0u8; 4097];
308 bytes[4096] = 1;
309
310 let outputs = reference_eval(&program, &[Value::from(bytes), Value::from(vec![0u8; 4])])
311 .expect("Fix: reference interpreter should execute singleton atomic flag scans.");
312 let flag = outputs[0].to_bytes();
313
314 assert_eq!(u32::from_le_bytes([flag[0], flag[1], flag[2], flag[3]]), 1);
315 }
316
317 #[test]
326 fn dispatch_floor_covers_packed_byte_scan_that_buffer_inference_under_covers() {
327 const PACKED_WORDS: u32 = 1024;
331 const BYTE_LEN: u32 = PACKED_WORDS * 4; const MARKER_POS: u32 = BYTE_LEN - 1; let program = Program::wrapped(
334 vec![
335 BufferDecl::storage("packed", 0, BufferAccess::ReadOnly, DataType::U32)
336 .with_count(PACKED_WORDS),
337 BufferDecl::storage("byte_len", 1, BufferAccess::ReadOnly, DataType::U32)
338 .with_count(1),
339 BufferDecl::storage("flag", 2, BufferAccess::ReadWrite, DataType::U32)
340 .with_count(1),
341 ],
342 [256, 1, 1],
343 vec![
344 Node::let_bind("i", Expr::InvocationId { axis: 0 }),
345 Node::if_then(
346 Expr::lt(Expr::var("i"), Expr::load("byte_len", Expr::u32(0))),
347 vec![Node::if_then(
348 Expr::eq(Expr::var("i"), Expr::u32(MARKER_POS)),
349 vec![
350 Node::let_bind(
354 "word",
355 Expr::load("packed", Expr::div(Expr::var("i"), Expr::u32(4))),
356 ),
357 Node::if_then(
358 Expr::eq(Expr::var("word"), Expr::u32(0)),
359 vec![Node::let_bind(
360 "flag_old",
361 Expr::atomic_or("flag", Expr::u32(0), Expr::u32(1)),
362 )],
363 ),
364 ],
365 )],
366 ),
367 ],
368 );
369 let read_flag = |outputs: &[Value]| {
370 let bytes = outputs[0].to_bytes();
371 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
372 };
373 let make_inputs = || {
374 vec![
375 Value::from(vec![0u8; PACKED_WORDS as usize * 4]),
376 Value::from(BYTE_LEN.to_le_bytes().to_vec()),
377 Value::from(vec![0u8; 4]),
378 ]
379 };
380
381 let under = reference_eval(&program, &make_inputs())
385 .expect("Fix: interpreter runs the packed byte-scan");
386 assert_eq!(
387 read_flag(&under),
388 0,
389 "buffer-shape grid inference must under-cover this packed byte-scan (documents the hole)"
390 );
391
392 let covered = reference_eval_with_dispatch(&program, &make_inputs(), BYTE_LEN)
395 .expect("Fix: interpreter runs the packed byte-scan with an explicit grid floor");
396 assert_eq!(
397 read_flag(&covered),
398 1,
399 "an explicit grid floor of haystack_len must cover every byte position"
400 );
401 }
402
403 #[test]
404 fn generic_storage_graph_matches_recursive_oracle_for_10k_programs() {
405 let mut rng = 0x9e37_79b9_u64;
406 for case in 0..10_000 {
407 let graph = random_graph(&mut rng, case);
408 let output = graph.last().expect("Fix: generated graph is non-empty").0;
409 let expected =
410 recursive_value(output, &graph).expect("Fix: recursive oracle evaluates");
411 let actual = run_storage_graph(&graph, &[output])
412 .expect("Fix: generic graph interpreter evaluates")[0];
413 assert_eq!(actual, expected, "case {case}");
414 }
415 }
416
417 fn random_graph(rng: &mut u64, case: u32) -> Vec<(NodeId, NodeStorage)> {
418 let len = 2 + (next(rng) as usize % 31);
419 let mut graph = Vec::with_capacity(len);
420 graph.push((NodeId(0), NodeStorage::LitU32(case)));
421 graph.push((NodeId(1), NodeStorage::LitU32(next(rng))));
422 for index in 2..len {
423 let left = NodeId(next(rng) % index as u32);
424 let right = NodeId(next(rng) % index as u32);
425 let op = match next(rng) % 5 {
426 0 => BinOp::Add,
427 1 => BinOp::Sub,
428 2 => BinOp::Mul,
429 3 => BinOp::BitXor,
430 _ => BinOp::BitAnd,
431 };
432 graph.push((NodeId(index as u32), NodeStorage::BinOp { op, left, right }));
433 }
434 graph
435 }
436
437 fn recursive_value(
438 id: NodeId,
439 graph: &[(NodeId, NodeStorage)],
440 ) -> Result<IrValue, vyre::Error> {
441 let node = graph
442 .iter()
443 .find(|(node_id, _)| *node_id == id)
444 .map(|(_, node)| node)
445 .ok_or_else(|| missing_node_error(id))?;
446 match node {
447 NodeStorage::LitU32(value) => Ok(IrValue::U32(*value)),
448 NodeStorage::BinOp { op, left, right } => {
449 let left = expect_u32(recursive_value(*left, graph)?)?;
450 let right = expect_u32(recursive_value(*right, graph)?)?;
451 let value = match op {
452 BinOp::Add => left.wrapping_add(right),
453 BinOp::Sub => left.wrapping_sub(right),
454 BinOp::Mul => left.wrapping_mul(right),
455 BinOp::BitXor => left ^ right,
456 BinOp::BitAnd => left & right,
457 _ => {
458 return Err(vyre::Error::interp(
459 "recursive parity oracle received unsupported op. Fix: keep test generation within the oracle domain.",
460 ));
461 }
462 };
463 Ok(IrValue::U32(value))
464 }
465 _ => Err(vyre::Error::interp(
466 "recursive parity oracle received unsupported node. Fix: keep test generation within the oracle domain.",
467 )),
468 }
469 }
470
471 fn expect_u32(value: IrValue) -> Result<u32, vyre::Error> {
472 match value {
473 IrValue::U32(value) => Ok(value),
474 other => Err(vyre::Error::interp(format!(
475 "recursive parity oracle expected u32, got {other:?}. Fix: keep generated graphs scalar-u32 only."
476 ))),
477 }
478 }
479
480 fn next(rng: &mut u64) -> u32 {
481 *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
482 (*rng >> 32) as u32
483 }
484}