1use std::collections::BTreeSet;
26use std::panic::{catch_unwind, AssertUnwindSafe};
27
28use serde::{Deserialize, Serialize};
29use serde_json::json;
30
31use super::super::ir::*;
32use super::super::passes::{SpatialReject, LV_ITEM_X, LV_ITEM_Y};
33use super::super::pipeline::run_passes;
34use super::super::rdu_chip::RduChip;
35use super::super::target::SpatialTarget;
36use crate::codegen_sva::fabric_rtl::{
37 decision_fabric_from_source, whole_program_fabric_from_source, CmpKind, DecisionFabric,
38 LoopCell, Src,
39};
40use crate::codegen_sva::fabric_target::Precision;
41use crate::codegen_sva::fabric_tile::{fabric_tile, frontier_reason};
42use crate::codegen_sva::plasticine::PlasticineTarget;
43use crate::codegen_sva::plasticine_report::{
44 counted_loop_fabric, lower_counted_loop, PlasticineArtifact, CERTIFICATE_WIDTH,
45};
46use crate::tensor_recognize::{
47 recognize_elementwise, recognize_fused_row, recognize_matmul, recognize_swiglu,
48};
49use crate::vm::{CompiledProgram, Compiler, Op};
50use logicaffeine_language::ast::stmt::Stmt;
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
54#[serde(rename_all = "kebab-case")]
55pub enum RduLane {
56 Tensor,
57 Loop,
58 WholeProgram,
59}
60
61impl RduLane {
62 pub const ALL: [RduLane; 3] = [RduLane::Tensor, RduLane::Loop, RduLane::WholeProgram];
63
64 pub fn as_str(self) -> &'static str {
66 match self {
67 RduLane::Tensor => "tensor",
68 RduLane::Loop => "loop",
69 RduLane::WholeProgram => "whole-program",
70 }
71 }
72
73 pub fn parse(s: &str) -> Option<RduLane> {
74 RduLane::ALL.into_iter().find(|l| l.as_str() == s.trim())
75 }
76}
77
78pub const WHOLE_PROGRAM_LANES: usize = 64;
81
82pub fn anchor_params(arity: usize) -> Vec<i64> {
85 (0..arity).map(|i| 8 + i as i64).collect()
86}
87
88fn own_function_names(source: &str) -> BTreeSet<String> {
95 source
96 .lines()
97 .filter_map(|line| {
98 let mut rest = line.trim().strip_prefix("## To ")?;
99 loop {
100 match rest
101 .strip_prefix("native ")
102 .or_else(|| rest.strip_prefix("private "))
103 {
104 Some(r) => rest = r,
105 None => break,
106 }
107 }
108 let name = rest.split(|c: char| c == '(' || c.is_whitespace()).next()?;
109 (!name.is_empty()).then(|| name.to_string())
110 })
111 .collect()
112}
113
114pub fn first_function(source: &str) -> Option<(String, usize)> {
118 let own = own_function_names(source);
119 if own.is_empty() {
120 return None;
121 }
122 crate::ui_bridge::with_parsed_program(source, |parsed, interner| {
123 let (stmts, _, _) = parsed.ok()?;
124 stmts.iter().find_map(|s| match s {
125 Stmt::FunctionDef { name, params, .. } => {
126 let name = interner.resolve(*name).to_string();
127 own.contains(&name).then_some((name, params.len()))
128 }
129 _ => None,
130 })
131 })
132}
133
134pub fn has_tensor_sugar(source: &str) -> bool {
137 source.lines().any(|line| {
138 let line = line.trim();
139 if line.starts_with('#') || !line.starts_with("Let ") || !line.contains(" be a ") {
140 return false;
141 }
142 let words: Vec<&str> = line.split_whitespace().collect();
143 words.windows(4).any(|w| {
144 w[0] == "by"
145 && w[1].parse::<u64>().is_ok()
146 && w[2] == "tensor"
147 && w[3].starts_with("of")
148 })
149 })
150}
151
152pub fn recognizes_a_tensor_op(source: &str) -> bool {
155 crate::ui_bridge::with_parsed_program(source, |parsed, interner| {
156 let Ok((stmts, _, _)) = parsed else {
157 return false;
158 };
159 recognize_matmul(stmts, interner).is_ok()
160 || recognize_elementwise(stmts, interner).is_ok()
161 || recognize_swiglu(stmts, interner).is_ok()
162 || recognize_fused_row(stmts, interner).is_ok()
163 })
164}
165
166pub fn lane_of(source: &str) -> RduLane {
170 if has_tensor_sugar(source) || recognizes_a_tensor_op(source) {
171 return RduLane::Tensor;
172 }
173 if let Some((name, _)) = first_function(source) {
174 if counted_loop_fabric(source, &name).is_ok() {
175 return RduLane::Loop;
176 }
177 }
178 RduLane::WholeProgram
179}
180
181#[derive(Debug)]
187pub struct LoopLaneArtifact {
188 pub placement_json: String,
190 pub report_json: String,
192 pub spatial_source: String,
194 pub certificate_json: String,
197 pub pretty: String,
199 pub plasticine: PlasticineArtifact,
202}
203
204pub fn lower_loop_lane(source: &str, chip: RduChip) -> Result<LoopLaneArtifact, String> {
208 let (fn_name, arity) = first_function(source).ok_or_else(|| {
209 "no region function found (the loop lane's unit is a counted-loop function)".to_string()
210 })?;
211 let params = anchor_params(arity);
212 let target = PlasticineTarget::rdu(chip);
213 let art = lower_counted_loop(source, &fn_name, ¶ms, &target)?;
214 let certificate = match (
215 &art.certificate,
216 &art.row.certificate,
217 &art.row.certificate_omitted,
218 ) {
219 (Some(pc), Some(row), _) => json!({
220 "kind": "sat-placement",
221 "proves": row.proves,
222 "steps": row.steps,
223 "vars": row.vars,
224 "width": CERTIFICATE_WIDTH,
225 "rechecked": pc.recheck(),
226 "digest": format!("{:016x}", pc.digest()),
227 }),
228 (_, _, Some(why)) => json!({ "kind": "omitted", "reason": why }),
229 _ => json!({ "kind": "omitted", "reason": "no certificate was produced" }),
230 };
231 let certificate_json = format!(
232 "{}\n",
233 serde_json::to_string_pretty(&certificate).expect("a certificate summary serializes")
234 );
235 let row = &art.row;
236 let mut pretty = String::new();
237 pretty.push_str("lane: loop\n");
238 pretty.push_str(&format!(
239 "program: `{fn_name}` ({arity} parameter{}) on {}\n",
240 if arity == 1 { "" } else { "s" },
241 target.name
242 ));
243 pretty.push_str(&format!(
244 "placement: pcu {}/{} pmu {}/{} ({}x{} checkerboard) router {} routed statically {}\n",
245 row.pcu_used,
246 row.pcu_total,
247 row.pmu_used,
248 row.pmu_total,
249 target.pcu_rows,
250 target.pcu_cols,
251 row.router,
252 row.routed_statically
253 ));
254 for s in &row.stages {
255 pretty.push_str(&format!(
256 "stage `{}`: II {} (resource {} recurrence {} memory {}) latency {} cells {} pcus {}\n",
257 s.name, s.ii, s.resource_ii, s.recurrence_ii, s.memory_ii, s.latency, s.cells, s.pcus
258 ));
259 }
260 pretty.push_str(&format!(
261 "metapipeline: II {} iterations {} fill {} compute {} modeled cycles {} (provenance {})\n",
262 row.pipeline_ii,
263 row.iterations,
264 row.fill_cycles,
265 row.compute_cycles,
266 row.modeled_cycles,
267 row.provenance
268 ));
269 if let Some(a) = &row.vm_anchor {
270 pretty.push_str(&format!(
271 "vm anchor: {fn_name}({}) = {} (VM) = {} (fabric) = {} (pipelined at II {}, hazards {})\n",
272 a.params.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "),
273 a.vm,
274 a.fabric,
275 a.pipelined_at_ii,
276 row.pipeline_ii,
277 a.hazards_at_ii
278 ));
279 }
280 match (&art.certificate, &row.certificate) {
281 (Some(pc), Some(c)) => pretty.push_str(&format!(
282 "certificate: SAT (PR/RUP) {} steps {} vars at {} bits, rechecked {}, digest {:016x}\n",
283 c.steps,
284 c.vars,
285 CERTIFICATE_WIDTH,
286 c.rechecked,
287 pc.digest()
288 )),
289 _ => pretty.push_str(&format!(
290 "certificate: omitted — {}\n",
291 row.certificate_omitted
292 .as_deref()
293 .unwrap_or("no certificate")
294 )),
295 }
296 pretty.push_str(&format!("spatial: Logos_{fn_name} (SpatialApp) + the Logos_{fn_name} regression app for the public PIR chain\n"));
297 Ok(LoopLaneArtifact {
298 placement_json: art.placement_json(),
299 report_json: art.row_json(),
300 spatial_source: art.spatial.clone(),
301 certificate_json,
302 pretty,
303 plasticine: art,
304 })
305}
306
307fn quietly<T>(f: impl FnOnce() -> T) -> Result<T, String> {
313 let prev = std::panic::take_hook();
314 std::panic::set_hook(Box::new(|_| {}));
315 let r = catch_unwind(AssertUnwindSafe(f));
316 std::panic::set_hook(prev);
317 r.map_err(|p| {
318 p.downcast_ref::<String>()
319 .cloned()
320 .or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string()))
321 .unwrap_or_else(|| "the lowering panicked".to_string())
322 })
323}
324
325enum Unit {
327 Function { name: String, arity: usize },
329 Main,
331}
332
333struct Scan {
334 unit: Unit,
335 loop_in: Option<String>,
337 recursion: Option<String>,
339 branch_in_main: bool,
341 frontier: Option<&'static str>,
343}
344
345fn function_range(program: &CompiledProgram, entry: usize) -> std::ops::Range<usize> {
347 let end = program
348 .functions
349 .iter()
350 .map(|g| g.entry_pc)
351 .filter(|&e| e > entry)
352 .min()
353 .unwrap_or(program.code.len());
354 entry..end
355}
356
357fn main_range(program: &CompiledProgram) -> std::ops::Range<usize> {
359 let first_fn = program
360 .functions
361 .iter()
362 .map(|f| f.entry_pc)
363 .min()
364 .unwrap_or(program.code.len());
365 let halt = program.code[..first_fn]
366 .iter()
367 .position(|op| matches!(op, Op::Halt))
368 .map(|i| i + 1)
369 .unwrap_or(first_fn);
370 0..halt
371}
372
373fn callees(program: &CompiledProgram, range: std::ops::Range<usize>) -> Vec<usize> {
374 program.code[range]
375 .iter()
376 .filter_map(|op| match op {
377 Op::Call { func, .. }
378 | Op::Spawn { func, .. }
379 | Op::SpawnHandle { func, .. }
380 | Op::MakeClosure { func, .. } => Some(*func as usize),
381 _ => None,
382 })
383 .collect()
384}
385
386fn has_back_edge(program: &CompiledProgram, range: std::ops::Range<usize>) -> bool {
387 let start = range.start;
388 program.code[range]
389 .iter()
390 .enumerate()
391 .any(|(i, op)| match op {
392 Op::Jump { target }
393 | Op::JumpIfFalse { target, .. }
394 | Op::JumpIfTrue { target, .. } => *target <= start + i,
395 _ => false,
396 })
397}
398
399fn scan(source: &str) -> Result<Scan, SpatialReject> {
401 let own = own_function_names(source);
402 crate::ui_bridge::with_parsed_program(source, |parsed, interner| {
403 let (stmts, types, _policies) = parsed.map_err(SpatialReject::Parse)?;
404 let program = Compiler::compile_with_types(stmts, interner, Some(types))
405 .map_err(SpatialReject::Parse)?;
406 let first = stmts.iter().find_map(|s| match s {
407 Stmt::FunctionDef { name, params, .. } => {
408 let name = interner.resolve(*name).to_string();
409 own.contains(&name).then_some((name, params.len()))
410 }
411 _ => None,
412 });
413 let name_of = |idx: usize| {
414 program
415 .functions
416 .get(idx)
417 .map(|f| interner.resolve(f.name).to_string())
418 .unwrap_or_else(|| format!("#{idx}"))
419 };
420 let (unit, own, roots): (Unit, std::ops::Range<usize>, Vec<usize>) = match &first {
422 Some((name, arity)) => {
423 let idx = program
424 .functions
425 .iter()
426 .position(|f| interner.resolve(f.name) == name)
427 .expect("the first function compiles");
428 let range = function_range(&program, program.functions[idx].entry_pc);
429 (
430 Unit::Function {
431 name: name.clone(),
432 arity: *arity,
433 },
434 range,
435 vec![idx],
436 )
437 }
438 None => {
439 let range = main_range(&program);
440 let roots = callees(&program, range.clone());
441 (Unit::Main, range, roots)
442 }
443 };
444 let branch_in_main = matches!(unit, Unit::Main)
445 && program.code[own.clone()].iter().any(|op| {
446 matches!(
447 op,
448 Op::Jump { .. } | Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. }
449 )
450 });
451 let mut loop_in =
453 if matches!(unit, Unit::Function { .. }) && has_back_edge(&program, own.clone()) {
454 match &unit {
455 Unit::Function { name, .. } => Some(name.clone()),
456 Unit::Main => None,
457 }
458 } else {
459 None
460 };
461 let mut recursion = None;
463 let mut visited: BTreeSet<usize> = BTreeSet::new();
464 let mut on_path: Vec<usize> = Vec::new();
465 fn dfs(
466 program: &CompiledProgram,
467 f: usize,
468 visited: &mut BTreeSet<usize>,
469 on_path: &mut Vec<usize>,
470 recursion: &mut Option<String>,
471 loop_in: &mut Option<String>,
472 name_of: &dyn Fn(usize) -> String,
473 ) {
474 if on_path.contains(&f) {
475 if recursion.is_none() {
476 *recursion = Some(name_of(f));
477 }
478 return;
479 }
480 if !visited.insert(f) {
481 return;
482 }
483 let Some(func) = program.functions.get(f) else {
484 return;
485 };
486 let range = function_range(program, func.entry_pc);
487 if loop_in.is_none() && has_back_edge(program, range.clone()) {
488 *loop_in = Some(name_of(f));
489 }
490 on_path.push(f);
491 for c in callees(program, range) {
492 dfs(program, c, visited, on_path, recursion, loop_in, name_of);
493 }
494 on_path.pop();
495 }
496 for r in roots {
497 dfs(
498 &program,
499 r,
500 &mut visited,
501 &mut on_path,
502 &mut recursion,
503 &mut loop_in,
504 &name_of,
505 );
506 }
507 let mut ranges = vec![own];
509 for f in &visited {
510 ranges.push(function_range(&program, program.functions[*f].entry_pc));
511 }
512 let frontier = ranges
513 .into_iter()
514 .flat_map(|r| program.code[r].iter())
515 .find_map(|op| {
516 if fabric_tile(op).is_none() {
517 Some(
518 frontier_reason(op)
519 .unwrap_or("an op with no fabric tile and no stated frontier reason"),
520 )
521 } else {
522 None
523 }
524 });
525 Ok(Scan {
526 unit,
527 loop_in,
528 recursion,
529 branch_in_main,
530 frontier,
531 })
532 })
533}
534
535fn scalar_program(
538 df: &DecisionFabric,
539 n: usize,
540 target: &SpatialTarget,
541 name: String,
542) -> Result<SpatialProgram, SpatialReject> {
543 if target.scalar_target().is_none() {
544 return Err(SpatialReject::MissingCompute {
545 needed: "scalar datapath",
546 target: target.name,
547 });
548 }
549 let k = df.n_in;
550 let mut cells = df.cells.clone();
553 let result = match df.result {
554 Src::Val(_) => df.result,
555 other => {
556 cells.push(LoopCell::Arith {
557 op: crate::codegen_sva::fabric_rtl::AluOp::Add,
558 a: other,
559 b: Src::Const(0),
560 });
561 Src::Val(cells.len() - 1)
562 }
563 };
564 let local = Handshake::Fifo;
565 let tile = target.tile;
566 let grid = |rows: usize, cols: usize| TileGrid {
567 rows_t: rows.div_ceil(tile.rows),
568 cols_t: cols.div_ceil(tile.cols),
569 };
570 let decl = |id: usize, name: String, role: TensorRole, init: TensorInit| TensorDecl {
571 id: TensorId::new(id),
572 name,
573 role,
574 elems: ElemShape { rows: 1, cols: n },
575 tiles: grid(1, n),
576 dtype: Precision::Int64,
577 layout: Layout::RowMajorTiles,
578 space: TensorSpace::Dram,
579 init,
580 range: None,
581 chip: None,
582 };
583 let mut tensors: Vec<TensorDecl> = (0..k)
584 .map(|i| decl(i, format!("x{i}"), TensorRole::Input, TensorInit::External))
585 .collect();
586 tensors.push(decl(k, "y".into(), TensorRole::Output, TensorInit::Zero));
587 let nt = tensors[k].tiles.cols_t;
588 let fifo = |id: usize, name: String| Buffer {
594 id: BufferId::new(id),
595 name,
596 kind: BufferKind::Fifo {
597 depth_pages: 2,
598 page_tiles: nt,
599 },
600 dtype: Precision::Int64,
601 space: MemSpace::Local,
602 site: None,
603 origin: None,
604 alias_of: None,
605 };
606 let mut buffers: Vec<Buffer> = (0..k).map(|i| fifo(i, format!("in{i}"))).collect();
607 buffers.push(fifo(k, "out".into()));
608 let pattern = || AccessPattern {
609 origin: TileAddr {
610 base: 0,
611 steps: vec![(LV_ITEM_Y, nt), (LV_ITEM_X, nt)],
612 indirect: None,
613 },
614 block_h: 1,
615 block_w: nt,
616 stride_w: 1,
617 stride_h: nt,
618 };
619 let mut stages: Vec<Stage> = (0..k)
620 .map(|i| Stage {
621 id: StageId::new(i),
622 name: format!("load_x{i}"),
623 kind: StageKind::Load {
624 tensor: TensorId::new(i),
625 pattern: pattern(),
626 num_blocks: 1,
627 multicast: None,
628 },
629 role: SiteRole::Movement,
630 ins: vec![],
631 outs: vec![BufferId::new(i)],
632 site: None,
633 item: None,
634 origin: None,
635 })
636 .collect();
637 let body = StageId::new(k);
638 stages.push(Stage {
639 id: body,
640 name: "body".into(),
641 kind: StageKind::Scalar(ScalarBody {
642 n_in: df.n_in,
643 cells,
644 result,
645 }),
646 role: SiteRole::Compute,
647 ins: (0..k).map(BufferId::new).collect(),
648 outs: vec![BufferId::new(k)],
649 site: None,
650 item: None,
651 origin: None,
652 });
653 let store = StageId::new(k + 1);
654 stages.push(Stage {
655 id: store,
656 name: "store_y".into(),
657 kind: StageKind::Store {
658 tensor: TensorId::new(k),
659 pattern: pattern(),
660 sub_blocks: (1, 1),
661 },
662 role: SiteRole::Movement,
663 ins: vec![BufferId::new(k)],
664 outs: vec![],
665 site: None,
666 item: None,
667 origin: None,
668 });
669 let mut edges: Vec<Edge> = (0..k)
670 .map(|i| Edge {
671 id: EdgeId::new(i),
672 from: StageId::new(i),
673 buffer: BufferId::new(i),
674 to: vec![body],
675 handshake: local.clone(),
676 })
677 .collect();
678 edges.push(Edge {
679 id: EdgeId::new(k),
680 from: body,
681 buffer: BufferId::new(k),
682 to: vec![store],
683 handshake: local.clone(),
684 });
685 let items = vec![WorkItem {
686 id: WorkItemId::new(0),
687 coord: (0, 0),
688 out: TileRect {
689 row0_t: 0,
690 col0_t: 0,
691 h_t: 1,
692 w_t: nt,
693 },
694 }];
695 let mut requires = Requirements::default();
696 requires.handshakes.insert(local.kind());
697 let p = SpatialProgram {
698 name,
699 tile,
700 tensors,
701 stages,
702 buffers,
703 edges,
704 semaphores: Vec::new(),
705 sections: Vec::new(),
706 trips: Vec::new(),
707 links: Vec::new(),
708 work: WorkList {
709 items,
710 k_tiles: 1,
711 plan: None,
712 k_trip: None,
713 },
714 numerics: NumericsPolicy::Exact,
715 claim: NumericsClaim::Exact,
716 acc_tiles_used: target.site.acc_tiles.max(1),
717 mem_banks: target.site.mem_banks,
718 requires,
719 placement: None,
720 memory: None,
721 schedule: None,
722 certificate: None,
723 };
724 p.validate()?;
725 Ok(p)
726}
727
728pub fn lower_whole_program_lane(
734 source: &str,
735 chip: RduChip,
736) -> Result<SpatialProgram, SpatialReject> {
737 let target = chip.facts().spatial_target();
738 let scan = scan(source)?;
739 if let Some(reason) = scan.frontier {
740 return Err(SpatialReject::MissingCompute {
741 needed: reason,
742 target: target.name,
743 });
744 }
745 if scan.recursion.is_some() {
746 return Err(SpatialReject::NotLowered(
747 "recursion: the first function reaches itself through the call graph — the decision front end inlines calls and would take the first-return path only; a spatial call stack is what a data-dependent recursion needs (the whole-program call-stack engine, `program_calls_fabric`, runs it ≡VM; a sequenced-program stage in the spatial IR is the expansion that lands it on the mesh)",
748 ));
749 }
750 if scan.loop_in.is_some() {
751 return Err(match &scan.unit {
752 Unit::Function { name, .. } if counted_loop_fabric(source, name).is_ok() => SpatialReject::NotLowered(
753 "a counted range loop is the loop lane's unit (`lower_loop_lane`: the Plasticine metapipeline on the RDU mesh descriptor), not a Scalar stage",
754 ),
755 _ => SpatialReject::NotLowered(
756 "a loop outside the loop lane's single-stage counted shape (a `While`, a nested loop, a loop over a sequence, a loop in a called function): the loop fabrics model it and have no spatial-IR stage form yet",
757 ),
758 });
759 }
760 if scan.branch_in_main {
761 return Err(SpatialReject::NotLowered("a branch at the top level of `Main` would make a `Show` conditional — the tape has no conditional lane; move the decision into a function"));
762 }
763 match scan.unit {
764 Unit::Function { name, arity } => {
765 if arity == 0 {
766 return Err(SpatialReject::NotLowered("a parameterless first function has no input vector to stream lane-wise — write its value as a `Show` in `Main` (the tape form) or give it a parameter"));
767 }
768 let df = quietly(|| decision_fabric_from_source(source, &name))
769 .map_err(SpatialReject::ScalarFrontEnd)?;
770 if df.escaped_first_return {
771 return Err(SpatialReject::NotLowered(
772 "an early return: a branch jumps past the first `Return`, and the decision front end would keep the first-return path only — write the function with one final `Return` and conditional `Set`s",
773 ));
774 }
775 let p = scalar_program(
776 &df,
777 WHOLE_PROGRAM_LANES,
778 &target,
779 format!("rdu_whole_program:{name}"),
780 )?;
781 run_passes(p, &target)
782 }
783 Unit::Main => {
784 let wpf = quietly(|| whole_program_fabric_from_source(source))
785 .map_err(SpatialReject::ScalarFrontEnd)?;
786 if wpf.outputs.is_empty() {
787 return Err(SpatialReject::NotLowered(
788 "a `Main` without a `Show` has no tape to lower",
789 ));
790 }
791 if wpf.outputs.iter().any(|(_, is_text, _)| *is_text) {
792 return Err(SpatialReject::NotLowered("a `Show` of Text has no integer lane on the tape — the tape-select stage carries Int values"));
793 }
794 if wpf.outputs.iter().any(|(_, _, guard)| guard.is_some()) {
795 return Err(SpatialReject::NotLowered("a policy `Check` guards the tape (a denied check halts it) — the tape-select stage has no conditional lane"));
796 }
797 let mut cells = wpf.cells.clone();
799 let k = wpf.outputs.len();
800 let mut acc = wpf.outputs[k - 1].0;
801 for i in (0..k - 1).rev() {
802 let idx = cells.len();
803 cells.push(LoopCell::Select {
804 cmp: CmpKind::Eq,
805 ca: Src::In(0),
806 cb: Src::Const(i as i64),
807 t: wpf.outputs[i].0,
808 e: acc,
809 });
810 acc = Src::Val(idx);
811 }
812 let df = DecisionFabric {
813 n_in: 1,
814 cells,
815 result: acc,
816 escaped_first_return: false,
817 };
818 let p = scalar_program(&df, k, &target, "rdu_whole_program:main".into())?;
819 run_passes(p, &target)
820 }
821 }
822}
823
824pub fn whole_program_inputs(p: &SpatialProgram) -> super::super::eval::Inputs {
827 use super::super::eval::{Inputs, Value};
828 let mut inputs = Inputs::default();
829 let n = p
830 .tensors
831 .iter()
832 .find(|t| t.role == TensorRole::Output)
833 .map(|t| t.elems.cols)
834 .unwrap_or(0);
835 let is_tape = p.name == "rdu_whole_program:main";
836 for (k, t) in p
837 .tensors
838 .iter()
839 .filter(|t| t.role == TensorRole::Input)
840 .enumerate()
841 {
842 let xs: Vec<i64> = if is_tape {
843 (0..n as i64).collect()
844 } else {
845 (0..n).map(|i| sample(k, i)).collect()
846 };
847 inputs.tensors.insert(t.id, Value::Int(xs));
848 }
849 inputs
850}
851
852pub fn sample(k: usize, i: usize) -> i64 {
855 let base = anchor_params(k + 1)[k];
856 if i == 0 {
857 return base;
858 }
859 let mut x = (i as u64)
860 .wrapping_mul(0x9E37_79B9_7F4A_7C15)
861 .wrapping_add(k as u64 + 1);
862 x ^= x >> 29;
863 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
864 x ^= x >> 32;
865 (x % 401) as i64 - 200
866}
867
868pub fn vm_anchor(source: &str, fn_name: &str, args: &[i64]) -> Result<i64, String> {
870 let call = args
871 .iter()
872 .map(|p| {
873 if *p < 0 {
874 format!("(0 - {})", -p)
875 } else {
876 p.to_string()
877 }
878 })
879 .collect::<Vec<_>>()
880 .join(", ");
881 let funcs = match source.find("## Main") {
882 Some(idx) => &source[..idx],
883 None => source,
884 };
885 let prog = format!("{funcs}## Main\n Show {fn_name}({call}).\n");
886 let out = crate::compile::vm_outcome(&prog);
887 if let Some(e) = out.error {
888 return Err(format!("the VM errored running `{fn_name}({call})`: {e}"));
889 }
890 out.output
891 .split_whitespace()
892 .next()
893 .and_then(|s| s.parse::<i64>().ok())
894 .ok_or_else(|| {
895 format!(
896 "the VM printed no integer for `{fn_name}({call})`: {:?}",
897 out.output
898 )
899 })
900}