1use std::collections::{BTreeMap, BTreeSet, VecDeque};
14
15use serde::{Deserialize, Serialize};
16
17use super::ir::*;
18use super::passes::{LV_ITEM, LV_ITEM_X, LV_ITEM_Y, LV_SUB_H, LV_SUB_W};
19use super::target::{precision_int_bits, precision_is_int};
20use crate::codegen_sva::fabric_rtl::{eval_decision_fabric_heap, DecisionFabric};
21use crate::codegen_sva::fabric_target::Precision;
22use crate::codegen_sva::simd::{f16_to_f32, f32_to_f16};
23use crate::codegen_sva::systolic::{round_bf16, trunc_signed};
24
25pub const INDIRECT_MAP_DEFERRED: &str = "indirect access (B-P5): an element-level scatter (an indirect term on a compute's output map) has no evaluation semantics";
29
30#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
32pub enum Value {
33 Int(Vec<i64>),
34 Float(Vec<f64>),
35}
36
37#[derive(Clone, Debug, Default)]
38pub struct Inputs {
39 pub tensors: BTreeMap<TensorId, Value>,
40}
41
42impl Inputs {
43 pub fn from_fills(p: &SpatialProgram) -> Result<Inputs, String> {
46 let mut inputs = Inputs::default();
47 for t in p.tensors.iter().filter(|t| t.role != TensorRole::Output) {
48 let n = t.elems.rows * t.elems.cols;
49 let value = if t.init.is_float() {
50 (0..n).map(|i| t.init.element_f64(i)).collect::<Option<Vec<f64>>>().map(Value::Float)
51 } else {
52 (0..n).map(|i| t.init.element(i)).collect::<Option<Vec<i64>>>().map(Value::Int)
53 };
54 inputs.tensors.insert(t.id, value.ok_or_else(|| format!("`{}` has no deterministic fill", t.name))?);
55 }
56 Ok(inputs)
57 }
58}
59
60#[derive(Clone, Debug, PartialEq)]
61pub struct Outputs {
62 pub tensors: BTreeMap<TensorId, Value>,
63 pub steps: u64,
64}
65
66#[derive(Clone, Debug, Default)]
67pub struct Trace {
68 pub high_water: BTreeMap<BufferId, usize>,
70 pub steps: u64,
71 pub tiles_read: BTreeMap<(TensorId, usize), u64>,
73 pub held: BTreeMap<BufferId, (i64, i64)>,
75 pub nodes: BTreeMap<StageId, Vec<Option<(i64, i64)>>>,
78 pub stage_held: BTreeMap<StageId, (i64, i64)>,
81}
82
83fn widen(slot: Option<(i64, i64)>, v: i64) -> (i64, i64) {
84 slot.map_or((v, v), |(lo, hi)| (lo.min(v), hi.max(v)))
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Scheduler {
89 RoundRobin,
90 Reverse,
91 Seeded(u64),
92 MovementFirst,
93 ComputeFirst,
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub enum ChoreoFault {
98 NotScheduled,
99 MissingInput(TensorId),
100 TripOutOfRange {
103 trip: TripId,
104 value: f64,
105 admitted: (i64, i64),
106 },
107 Deadlock {
108 runnable: Vec<(Site, Engine)>,
109 blocked_at: Vec<(Site, Engine, usize, ChoreoOp)>,
110 },
111 BufferOverflow {
112 buf: BufferId,
113 at: (Site, Engine, usize),
114 },
115 Underflow {
116 buf: BufferId,
117 at: (Site, Engine, usize),
118 },
119 PushWithoutData {
120 buf: BufferId,
121 at: (Site, Engine, usize),
122 },
123 ReadBeforeWait {
125 buf: BufferId,
126 at: (Site, Engine, usize),
127 },
128 PopBeforeWait {
130 buf: BufferId,
131 at: (Site, Engine, usize),
132 },
133 Unconsumed {
134 buf: BufferId,
135 pages_left: usize,
136 },
137 OutstandingReservation {
138 buf: BufferId,
139 pages: usize,
140 },
141 McastIntoUnreserved {
142 group: GroupId,
143 receiver: Site,
144 },
145 SemaphoreProtocol {
146 sem: SemId,
147 expected: SemCond,
148 found: usize,
149 },
150 AccOverflow {
151 requested: usize,
152 available: usize,
153 },
154 AliasClobber {
158 buf: BufferId,
159 alias: BufferId,
160 at: (Site, Engine, usize),
161 },
162 AccProtocol {
163 at: (Site, Engine, usize),
164 },
165 StoreConflict {
166 tensor: TensorId,
167 tile: usize,
168 first: Site,
169 second: Site,
170 },
171 OutputTileNeverStored {
172 tensor: TensorId,
173 tile: usize,
174 },
175 ReadOutOfRange {
176 tensor: TensorId,
177 tile: usize,
178 },
179 StaleStateRead {
190 tensor: TensorId,
191 tile: usize,
192 read_in: usize,
193 written_in: usize,
194 already_written: bool,
195 },
196 NonDeterministic {
197 scheduler_a: &'static str,
198 scheduler_b: &'static str,
199 tensor: TensorId,
200 },
201 GatherOutOfRange {
206 at: (Site, Engine, usize),
207 index: f64,
208 extent: usize,
209 },
210 IntegerArithmetic {
214 at: (Site, Engine, usize),
215 what: &'static str,
216 },
217 TileGatherOutOfBound {
221 at: (Site, Engine, usize),
222 index: f64,
223 bound: (usize, usize),
224 },
225 Unsupported(String),
226}
227
228#[derive(Clone, Debug)]
229enum TileData {
230 Int(Vec<i64>),
231 Float(Vec<f64>),
232 Empty,
234}
235
236type Page = Vec<TileData>;
237
238#[derive(Clone, Debug)]
239enum AccData {
240 Int(Vec<i64>),
241 F32(Vec<f32>),
242}
243
244struct FifoState {
245 capacity: usize,
246 queue: VecDeque<Page>,
247 pending: VecDeque<Page>,
248 reserved: usize,
249 high_water: usize,
250 visible: usize,
252 popped: usize,
256 slots: Vec<Page>,
261 wr: usize,
262}
263
264impl FifoState {
265 fn resident(&self) -> usize {
266 self.queue.len() + self.pending.len()
267 }
268}
269
270struct TensorStore {
271 dtype: Precision,
272 cols_t: usize,
273 tiles: Vec<Option<TileData>>,
274 stored_by: Vec<Option<(Site, usize)>>,
276 writer_sections: Option<Vec<usize>>,
279 written_in: Vec<Option<usize>>,
281 elided: bool,
284}
285
286#[derive(Clone, Debug)]
287enum Instr {
288 Op(ChoreoOp, StageId),
292 LoopStart {
293 var: LoopVar,
294 count: usize,
295 end: usize,
296 },
297 LoopEnd {
298 start: usize,
299 },
300}
301
302struct Coroutine {
303 site: Site,
304 engine: Engine,
305 code: std::rc::Rc<Vec<Instr>>,
306 pc: usize,
307 loops: Vec<(LoopVar, usize, usize)>,
308 env: BTreeMap<LoopVar, usize>,
309 acc: Option<Vec<AccData>>,
310 acc_computed: bool,
312 acc_folded: std::collections::BTreeSet<(usize, usize)>,
320 done: bool,
321}
322
323fn count_writes(p: &SpatialProgram, code: &[Instr], out: &mut [BTreeMap<usize, u64>]) {
326 let mut mult: Vec<u64> = vec![1];
327 for i in code {
328 match i {
329 Instr::LoopStart { count, .. } => {
330 let m = *mult.last().expect("an outer multiplicity");
331 mult.push(m * *count as u64);
332 }
333 Instr::LoopEnd { .. } => {
334 mult.pop();
335 }
336 Instr::Op(ChoreoOp::WriteTiles { tensor, .. }, stage) => {
337 *out[tensor.idx()]
338 .entry(p.section_of_stage(*stage).unwrap_or(0))
339 .or_default() += *mult.last().expect("an outer multiplicity")
340 }
341 _ => {}
342 }
343 }
344}
345
346fn flatten(
350 ops: &[TaggedOp],
351 out: &mut Vec<Instr>,
352 counts: &[usize],
353 item_cap: Option<usize>,
354 elided: &mut [bool],
355) {
356 for t in ops {
357 match &t.op {
358 ChoreoOp::Loop { var, count, body } => {
359 let start = out.len();
360 let full = count.at(counts);
361 let n = match item_cap {
366 Some(cap) if *var == LV_ITEM => full.min(cap),
367 _ => full,
368 };
369 if n < full {
370 walk_tagged_ops(body, 1, &mut |op, _| {
371 if let ChoreoOp::WriteTiles { tensor, .. } = op {
372 elided[tensor.idx()] = true;
373 }
374 });
375 }
376 out.push(Instr::LoopStart {
377 var: *var,
378 count: n,
379 end: 0,
380 });
381 flatten(body, out, counts, item_cap, elided);
382 let end = out.len();
383 out.push(Instr::LoopEnd { start });
384 if let Instr::LoopStart { end: e, .. } = &mut out[start] {
385 *e = end;
386 }
387 }
388 other => out.push(Instr::Op(other.clone(), t.stage)),
389 }
390 }
391}
392
393fn scheduler_name(s: Scheduler) -> &'static str {
394 match s {
395 Scheduler::RoundRobin => "round-robin",
396 Scheduler::Reverse => "reverse",
397 Scheduler::Seeded(_) => "seeded",
398 Scheduler::MovementFirst => "movement-first",
399 Scheduler::ComputeFirst => "compute-first",
400 }
401}
402
403fn add_pages(base: &Page, add: &Page) -> Page {
405 if base.len() != add.len() {
406 return add.clone();
407 }
408 base.iter()
409 .zip(add)
410 .map(|(b, a)| match (b, a) {
411 (TileData::Int(x), TileData::Int(y)) => {
412 TileData::Int(x.iter().zip(y).map(|(u, v)| u.wrapping_add(*v)).collect())
413 }
414 (TileData::Float(x), TileData::Float(y)) => {
415 TileData::Float(x.iter().zip(y).map(|(u, v)| u + v).collect())
416 }
417 (TileData::Empty, other) | (other, TileData::Empty) => other.clone(),
418 (_, y) => y.clone(),
419 })
420 .collect()
421}
422
423fn narrow(tile: TileData, p: Precision) -> TileData {
425 match tile {
426 TileData::Float(v) => TileData::Float(v.into_iter().map(|x| round_to(p, x)).collect()),
427 other => other,
428 }
429}
430
431pub fn round_to(p: Precision, x: f64) -> f64 {
433 match p {
434 Precision::Bf16 => round_bf16(x),
435 Precision::Fp16 => f16_to_f32(f32_to_f16(x as f32)) as f64,
436 Precision::Fp32 => x as f32 as f64,
437 _ => x,
438 }
439}
440
441struct Eval<'a> {
442 p: &'a SpatialProgram,
443 t: usize,
444 fifos: Vec<FifoState>,
445 tensors: Vec<TensorStore>,
446 sems: BTreeMap<(SemId, Site), usize>,
447 recv_buffer: BTreeMap<(GroupId, Site), BufferId>,
448 programs: Vec<Coroutine>,
449 site_items: SiteItems<'a>,
451 writes_expected: Vec<BTreeMap<usize, u64>>,
453 writes_done: Vec<BTreeMap<usize, u64>>,
454 steps: u64,
455 dry: bool,
457 tiles_read: BTreeMap<(TensorId, usize), u64>,
459 partners: Vec<Vec<BufferId>>,
461 held: BTreeMap<BufferId, (i64, i64)>,
462 nodes: BTreeMap<StageId, Vec<Option<(i64, i64)>>>,
463 stage_held: BTreeMap<StageId, (i64, i64)>,
464}
465
466impl<'a> Eval<'a> {
467 fn new(p: &'a SpatialProgram, inputs: &Inputs) -> Result<Eval<'a>, ChoreoFault> {
468 let counts = trip_counts(p, inputs)?;
469 Self::build(p, inputs, false, &counts)
470 }
471
472 fn new_dry(p: &'a SpatialProgram, counts: &[usize]) -> Result<Eval<'a>, ChoreoFault> {
473 Self::build(p, &Inputs::default(), true, counts)
474 }
475
476 fn build(
477 p: &'a SpatialProgram,
478 inputs: &Inputs,
479 dry: bool,
480 counts: &[usize],
481 ) -> Result<Eval<'a>, ChoreoFault> {
482 let sched = p.schedule.as_ref().ok_or(ChoreoFault::NotScheduled)?;
483 let t = p.tile.rows;
484 assert_eq!(p.tile.rows, p.tile.cols, "square native tiles");
485 let mut store_sections: Vec<Vec<usize>> = vec![Vec::new(); p.tensors.len()];
486 for s in &p.stages {
487 if let StageKind::Store { tensor, .. } = &s.kind {
488 if let Some(v) = store_sections.get_mut(tensor.idx()) {
489 v.push(p.section_of_stage(s.id).unwrap_or(0));
490 }
491 }
492 }
493 let mut tensors = Vec::new();
494 for td in &p.tensors {
495 let n_tiles = td.tiles.count();
496 let writer_sections = (td.role == TensorRole::State).then(|| {
500 let mut w = store_sections.get(td.id.idx()).cloned().unwrap_or_default();
501 w.sort_unstable();
502 w.dedup();
503 w
504 });
505 let mut store = TensorStore {
506 dtype: td.dtype,
507 cols_t: td.tiles.cols_t,
508 tiles: vec![None; n_tiles],
509 stored_by: vec![None; n_tiles],
510 writer_sections,
511 written_in: vec![None; n_tiles],
512 elided: false,
513 };
514 if td.role.starts_filled() && dry {
515 store.tiles = vec![Some(TileData::Empty); n_tiles];
516 } else if td.role.starts_filled() {
517 let value = match inputs.tensors.get(&td.id) {
518 Some(v) => v.clone(),
519 None => {
520 let n = td.elems.rows * td.elems.cols;
521 if td.init.is_float() {
522 let vals: Option<Vec<f64>> =
523 (0..n).map(|i| td.init.element_f64(i)).collect();
524 Value::Float(vals.ok_or(ChoreoFault::MissingInput(td.id))?)
525 } else {
526 let vals: Option<Vec<i64>> =
527 (0..n).map(|i| td.init.element(i)).collect();
528 Value::Int(vals.ok_or(ChoreoFault::MissingInput(td.id))?)
529 }
530 }
531 };
532 let (rows, cols) = (td.elems.rows, td.elems.cols);
533 let is_int = precision_is_int(td.dtype);
534 let bits = precision_int_bits(td.dtype).unwrap_or(64);
535 for rt in 0..td.tiles.rows_t {
536 for ct in 0..td.tiles.cols_t {
537 let mut ints = Vec::new();
538 let mut floats = Vec::new();
539 for r in 0..t {
540 for c in 0..t {
541 let (er, ec) = (rt * t + r, ct * t + c);
542 let inside = er < rows && ec < cols;
543 let idx = er * cols + ec;
544 match (&value, is_int) {
545 (Value::Int(v), true) => ints.push(if inside {
546 trunc_signed(v[idx], bits)
547 } else {
548 0
549 }),
550 (Value::Int(v), false) => floats.push(if inside {
551 round_to(td.dtype, v[idx] as f64)
552 } else {
553 0.0
554 }),
555 (Value::Float(v), false) => floats.push(if inside {
556 round_to(td.dtype, v[idx])
557 } else {
558 0.0
559 }),
560 (Value::Float(v), true) => ints.push(if inside {
561 trunc_signed(v[idx] as i64, bits)
562 } else {
563 0
564 }),
565 }
566 }
567 }
568 let tile = if is_int {
569 TileData::Int(ints)
570 } else {
571 TileData::Float(floats)
572 };
573 store.tiles[rt * td.tiles.cols_t + ct] = Some(tile);
574 }
575 }
576 }
577 tensors.push(store);
578 }
579 let fifos = p
580 .buffers
581 .iter()
582 .map(|b| FifoState {
583 capacity: b.kind.depth_pages(),
584 slots: vec![Vec::new(); b.kind.depth_pages().max(1)],
585 wr: 0,
586 popped: 0,
587 queue: VecDeque::new(),
588 pending: VecDeque::new(),
589 reserved: 0,
590 high_water: 0,
591 visible: 0,
592 })
593 .collect();
594 let mut recv_buffer = BTreeMap::new();
595 for s in &p.stages {
596 if let (StageKind::Recv { group }, Some(site)) = (&s.kind, s.site) {
597 if let Some(out) = s.outs.first() {
598 recv_buffer.insert((*group, site), *out);
599 }
600 }
601 }
602 let mut programs = Vec::new();
603 let mut writes_expected = vec![BTreeMap::new(); p.tensors.len()];
604 let mut elided = vec![false; p.tensors.len()];
605 for prog in &sched.programs {
606 let mut code = Vec::new();
607 flatten(
608 &prog.ops,
609 &mut code,
610 counts,
611 if dry {
612 Some(crate::spatial::certificate::item_window(p))
613 } else {
614 None
615 },
616 &mut elided,
617 );
618 count_writes(p, &code, &mut writes_expected);
619 programs.push(Coroutine {
620 site: prog.site,
621 engine: prog.engine,
622 code: std::rc::Rc::new(code),
623 pc: 0,
624 loops: Vec::new(),
625 env: BTreeMap::new(),
626 acc: None,
627 acc_computed: false,
628 acc_folded: Default::default(),
629 done: false,
630 });
631 }
632 for (store, cut) in tensors.iter_mut().zip(elided) {
633 store.elided = cut;
634 }
635 let writes_done = vec![BTreeMap::new(); p.tensors.len()];
636 let mut regions: BTreeMap<BufferId, Vec<BufferId>> = BTreeMap::new();
637 for b in &p.buffers {
638 if let Some(owner) = b.alias_of {
639 regions
640 .entry(owner)
641 .or_insert_with(|| vec![owner])
642 .push(b.id);
643 }
644 }
645 let mut partners = vec![Vec::new(); p.buffers.len()];
646 for sharing in regions.values() {
647 for &b in sharing {
648 partners[b.idx()] = sharing.iter().copied().filter(|o| *o != b).collect();
649 }
650 }
651 Ok(Eval {
652 p,
653 t,
654 fifos,
655 tensors,
656 sems: BTreeMap::new(),
657 recv_buffer,
658 programs,
659 site_items: p.site_items(),
660 writes_expected,
661 writes_done,
662 steps: 0,
663 dry,
664 tiles_read: BTreeMap::new(),
665 partners,
666 held: BTreeMap::new(),
667 nodes: BTreeMap::new(),
668 stage_held: BTreeMap::new(),
669 })
670 }
671
672 fn item_coord(
673 &self,
674 site: Site,
675 env: &BTreeMap<LoopVar, usize>,
676 stage: StageId,
677 ) -> (usize, usize) {
678 let items = self.site_items.at(self.p.section_of_stage(stage), site);
679 let idx = env.get(&LV_ITEM).copied().unwrap_or(0);
680 match items.get(idx) {
681 Some(id) => self.p.work.items[id.idx()].coord,
682 None => (0, 0),
683 }
684 }
685
686 fn resolve_addr(
690 &self,
691 site: Site,
692 env: &BTreeMap<LoopVar, usize>,
693 addr: &TileAddr,
694 stage: StageId,
695 at: (Site, Engine, usize),
696 ) -> Result<usize, ChoreoFault> {
697 let (oy, ox) = self.item_coord(site, env, stage);
698 let mut a = addr.base;
699 for (v, stride) in &addr.steps {
700 let idx = if *v == LV_ITEM_Y {
701 oy
702 } else if *v == LV_ITEM_X {
703 ox
704 } else {
705 env.get(v).copied().unwrap_or(0)
706 };
707 a += idx * stride;
708 }
709 if let Some(ind) = &addr.indirect {
710 a += self.indirect_offset(ind, at)?;
711 }
712 Ok(a)
713 }
714
715 fn indirect_offset(
723 &self,
724 ind: &IndirectTile,
725 at: (Site, Engine, usize),
726 ) -> Result<usize, ChoreoFault> {
727 let v = if self.dry {
728 ind.bound.0 as f64
729 } else {
730 let page = self.front_page(ind.index, at)?;
731 match page.first() {
732 Some(TileData::Int(v)) => v.first().copied().unwrap_or(0) as f64,
733 Some(TileData::Float(v)) => v.first().copied().unwrap_or(0.0),
734 _ => {
735 return Err(ChoreoFault::Unsupported(
736 "an indirect tile index page holds no data".into(),
737 ))
738 }
739 }
740 };
741 if v.fract() != 0.0 || v < ind.bound.0 as f64 || v > ind.bound.1 as f64 {
742 return Err(ChoreoFault::TileGatherOutOfBound {
743 at,
744 index: v,
745 bound: ind.bound,
746 });
747 }
748 Ok((v as usize / ind.elems_per_tile) * ind.tile_stride)
749 }
750
751 fn check_indirect_span(
754 &self,
755 addr: &TileAddr,
756 base: usize,
757 block: (usize, usize, usize, usize),
758 tensor: TensorId,
759 ) -> Result<(), ChoreoFault> {
760 let Some(ind) = &addr.indirect else {
761 return Ok(());
762 };
763 let (block_h, block_w, stride_h, stride_w) = (block.0, block.1, block.2, block.3);
764 let low = (ind.bound.0 / ind.elems_per_tile) * ind.tile_stride;
765 let high = (ind.bound.1 / ind.elems_per_tile) * ind.tile_stride;
766 let top = base - low + high;
767 let last =
768 top + block_h.saturating_sub(1) * stride_h + block_w.saturating_sub(1) * stride_w;
769 let tiles = self.tensors[tensor.idx()].tiles.len();
770 if last >= tiles {
771 return Err(ChoreoFault::ReadOutOfRange { tensor, tile: last });
772 }
773 Ok(())
774 }
775
776 fn storage(&self, buf: BufferId) -> BufferId {
778 self.p.buffer(buf).alias_of.unwrap_or(buf)
779 }
780
781 fn check_alias_clobber(
784 &self,
785 buf: BufferId,
786 slot: usize,
787 at: (Site, Engine, usize),
788 ) -> Result<(), ChoreoFault> {
789 let len = self.fifos[self.storage(buf).idx()].slots.len().max(1);
790 for &alias in &self.partners[buf.idx()] {
791 let f = &self.fifos[alias.idx()];
792 if (f.popped..f.wr).any(|n| n % len == slot) {
793 return Err(ChoreoFault::AliasClobber { buf, alias, at });
794 }
795 }
796 Ok(())
797 }
798
799 fn touch(&mut self, buf: BufferId) {
800 let f = &mut self.fifos[buf.idx()];
801 f.high_water = f.high_water.max(f.resident());
802 let mut held = self.held.get(&buf).copied();
804 for tile in f.pending.iter().flatten() {
805 if let TileData::Int(v) = tile {
806 for x in v {
807 held = Some(widen(held, *x));
808 }
809 }
810 }
811 if let Some(h) = held {
812 self.held.insert(buf, h);
813 }
814 }
815
816 fn note_acc(&mut self, pi: usize, stage: StageId) {
818 let Some(acc) = self.programs[pi].acc.as_ref() else { return };
819 let mut held = self.stage_held.get(&stage).copied();
820 for a in acc {
821 if let AccData::Int(v) = a {
822 for x in v {
823 held = Some(widen(held, *x));
824 }
825 }
826 }
827 if let Some(h) = held {
828 self.stage_held.insert(stage, h);
829 }
830 }
831
832 fn step(&mut self, pi: usize) -> Result<bool, ChoreoFault> {
834 let (site, engine, pc) = {
835 let c = &self.programs[pi];
836 if c.done {
837 return Ok(false);
838 }
839 (c.site, c.engine, c.pc)
840 };
841 let at = (site, engine, pc);
842 let code = std::rc::Rc::clone(&self.programs[pi].code);
843 let Some(instr) = code.get(pc) else {
844 self.programs[pi].done = true;
845 return Ok(true);
846 };
847 match instr {
848 Instr::LoopStart { var, count, end } => {
849 let (var, count, end) = (*var, *count, *end);
850 let c = &mut self.programs[pi];
851 if count == 0 {
852 c.pc = end + 1;
853 } else {
854 c.loops.push((var, 0, count));
855 c.env.insert(var, 0);
856 c.pc += 1;
857 }
858 Ok(true)
859 }
860 Instr::LoopEnd { start } => {
861 let start = *start;
862 let c = &mut self.programs[pi];
863 let (var, iter, count) = c.loops.pop().expect("loop frame");
864 if iter + 1 < count {
865 c.loops.push((var, iter + 1, count));
866 c.env.insert(var, iter + 1);
867 c.pc = start + 1;
868 } else {
869 c.env.remove(&var);
870 c.pc += 1;
871 }
872 Ok(true)
873 }
874 Instr::Op(op, stage) => {
875 let stage = *stage;
876 let stepped = self.exec(pi, site, engine, at, &op, stage)?;
877 if stepped {
878 self.programs[pi].pc += 1;
879 }
880 Ok(stepped)
881 }
882 }
883 }
884
885 fn exec(
886 &mut self,
887 pi: usize,
888 site: Site,
889 engine: Engine,
890 at: (Site, Engine, usize),
891 op: &ChoreoOp,
892 stage: StageId,
893 ) -> Result<bool, ChoreoFault> {
894 match op {
895 ChoreoOp::Reserve { buf, pages } => {
896 let f = &mut self.fifos[buf.idx()];
897 if f.queue.len() + f.reserved + *pages > f.capacity {
898 return Ok(false);
899 }
900 f.reserved += *pages;
901 Ok(true)
902 }
903 ChoreoOp::Push { buf, pages } => {
904 {
905 let f = &self.fifos[buf.idx()];
906 if f.reserved < *pages {
907 return Err(ChoreoFault::BufferOverflow { buf: *buf, at });
908 }
909 if f.pending.len() < *pages {
910 return Err(ChoreoFault::PushWithoutData { buf: *buf, at });
911 }
912 }
913 let owner = self.storage(*buf);
914 for _ in 0..*pages {
915 let slot =
916 self.fifos[buf.idx()].wr % self.fifos[owner.idx()].slots.len().max(1);
917 self.check_alias_clobber(*buf, slot, at)?;
918 let f = &mut self.fifos[buf.idx()];
919 let page = f.pending.pop_front().unwrap();
920 f.wr = f.wr.wrapping_add(1);
921 f.queue.push_back(page.clone());
922 let o = &mut self.fifos[owner.idx()];
923 if !o.slots.is_empty() {
924 o.slots[slot] = page;
925 }
926 }
927 self.fifos[buf.idx()].reserved -= *pages;
928 self.touch(*buf);
929 Ok(true)
930 }
931 ChoreoOp::Wait { buf, pages } => {
932 let f = &mut self.fifos[buf.idx()];
933 if f.queue.len() < *pages {
934 return Ok(false);
935 }
936 f.visible = f.visible.max(*pages);
937 Ok(true)
938 }
939 ChoreoOp::Pop { buf, pages } => {
940 let f = &mut self.fifos[buf.idx()];
941 if f.queue.len() < *pages {
942 return Err(ChoreoFault::Underflow { buf: *buf, at });
943 }
944 if f.visible < *pages {
945 return Err(ChoreoFault::PopBeforeWait { buf: *buf, at });
946 }
947 for _ in 0..*pages {
948 f.queue.pop_front();
949 }
950 f.visible -= *pages;
951 f.popped += *pages;
952 Ok(true)
953 }
954 ChoreoOp::ReadTiles {
955 tensor,
956 at: addr,
957 block_h,
958 block_w,
959 stride_w,
960 stride_h,
961 into,
962 } => {
963 let base = self.resolve_addr(site, &self.programs[pi].env, addr, stage, at)?;
964 if self.dry {
965 self.check_indirect_span(
966 addr,
967 base,
968 (*block_h, *block_w, *stride_h, *stride_w),
969 *tensor,
970 )?;
971 }
972 let read_in = self.p.section_of_stage(stage).unwrap_or(0);
973 *self.tiles_read.entry((*tensor, read_in)).or_default() +=
974 (*block_h * *block_w) as u64;
975 let dst_dtype = self.p.buffer(*into).dtype;
976 let store = &self.tensors[tensor.idx()];
977 let mut page = Vec::with_capacity(block_h * block_w);
978 for h in 0..*block_h {
979 for w in 0..*block_w {
980 let idx = base + h * stride_h + w * stride_w;
981 if let Some(writers) = &store.writer_sections {
996 let last = store.written_in.get(idx).copied().flatten();
997 let done = |w: usize| {
998 let t = tensor.idx();
999 self.writes_done[t].get(&w).copied().unwrap_or(0)
1000 >= self.writes_expected[t].get(&w).copied().unwrap_or(0)
1001 };
1002 let late = last.filter(|l| *l >= read_in);
1003 let early = writers
1004 .iter()
1005 .copied()
1006 .find(|w| *w < read_in && !done(*w) && last.is_none_or(|l| l < *w));
1007 if let Some(w_sec) = late.or(early) {
1008 return Err(ChoreoFault::StaleStateRead {
1009 tensor: *tensor,
1010 tile: idx,
1011 read_in,
1012 written_in: w_sec,
1013 already_written: last.is_some(),
1014 });
1015 }
1016 }
1017 let tile = match store.tiles.get(idx) {
1021 Some(Some(tile)) => tile.clone(),
1022 Some(None) if store.elided => TileData::Empty,
1023 _ => {
1024 return Err(ChoreoFault::ReadOutOfRange {
1025 tensor: *tensor,
1026 tile: idx,
1027 })
1028 }
1029 };
1030 page.push(if self.dry {
1038 TileData::Empty
1039 } else {
1040 narrow(tile, dst_dtype)
1041 });
1042 }
1043 }
1044 let f = &mut self.fifos[into.idx()];
1045 if f.pending.len() >= f.reserved {
1046 return Err(ChoreoFault::BufferOverflow { buf: *into, at });
1047 }
1048 f.pending.push_back(page);
1049 self.touch(*into);
1050 Ok(true)
1051 }
1052 ChoreoOp::ReadBarrier | ChoreoOp::WriteBarrier | ChoreoOp::Flush { .. } => Ok(true),
1053 ChoreoOp::WriteTiles {
1054 from,
1055 tensor,
1056 at: addr,
1057 block_h,
1058 block_w,
1059 stride_w,
1060 stride_h,
1061 } => {
1062 let base = self.resolve_addr(site, &self.programs[pi].env, addr, stage, at)?;
1063 if self.dry {
1064 self.check_indirect_span(
1065 addr,
1066 base,
1067 (*block_h, *block_w, *stride_h, *stride_w),
1068 *tensor,
1069 )?;
1070 }
1071 let page = self.front_page(*from, at)?;
1072 if page.len() != block_h * block_w {
1073 return Err(ChoreoFault::Unsupported(format!(
1074 "write page of {} tiles for a {block_h}x{block_w} block",
1075 page.len()
1076 )));
1077 }
1078 let section = self.p.section_of_stage(stage).unwrap_or(0);
1079 let store = &mut self.tensors[tensor.idx()];
1080 for h in 0..*block_h {
1081 for w in 0..*block_w {
1082 let idx = base + h * stride_h + w * stride_w;
1083 if idx >= store.tiles.len() {
1084 return Err(ChoreoFault::ReadOutOfRange {
1085 tensor: *tensor,
1086 tile: idx,
1087 });
1088 }
1089 if let Some((first, first_section)) = store.stored_by[idx] {
1094 if store.writer_sections.is_none() || first_section == section {
1095 return Err(ChoreoFault::StoreConflict {
1096 tensor: *tensor,
1097 tile: idx,
1098 first,
1099 second: site,
1100 });
1101 }
1102 }
1103 store.tiles[idx] = Some(page[h * block_w + w].clone());
1104 store.stored_by[idx] = Some((site, section));
1105 store.written_in[idx] = Some(section);
1106 }
1107 }
1108 *self.writes_done[tensor.idx()].entry(section).or_default() += 1;
1109 Ok(true)
1110 }
1111 ChoreoOp::McastSend {
1112 buf, pages, group, ..
1113 } => {
1114 let pl = self.p.placement.as_ref().expect("placed");
1115 let g = &pl.groups[group.idx()];
1116 let sender_pending: Vec<Page> = {
1117 let f = &self.fifos[buf.idx()];
1118 if f.pending.len() < *pages {
1119 return Err(ChoreoFault::PushWithoutData { buf: *buf, at });
1120 }
1121 f.pending.iter().rev().take(*pages).rev().cloned().collect()
1122 };
1123 let receivers: Vec<Site> = g
1124 .rect
1125 .sites()
1126 .into_iter()
1127 .filter(|s| *s != g.sender)
1128 .collect();
1129 for r in &receivers {
1130 let rb = *self.recv_buffer.get(&(*group, *r)).ok_or(
1131 ChoreoFault::McastIntoUnreserved {
1132 group: *group,
1133 receiver: *r,
1134 },
1135 )?;
1136 let f = &self.fifos[rb.idx()];
1137 if f.pending.len() + pages > f.reserved {
1138 return Err(ChoreoFault::McastIntoUnreserved {
1139 group: *group,
1140 receiver: *r,
1141 });
1142 }
1143 }
1144 for r in &receivers {
1145 let rb = self.recv_buffer[&(*group, *r)];
1146 let f = &mut self.fifos[rb.idx()];
1147 for page in &sender_pending {
1148 f.pending.push_back(page.clone());
1149 }
1150 self.touch(rb);
1151 }
1152 Ok(true)
1153 }
1154 ChoreoOp::McastSetValid { sem, group } => {
1155 let pl = self.p.placement.as_ref().expect("placed");
1156 let g = &pl.groups[group.idx()];
1157 for r in g.rect.sites().into_iter().filter(|s| *s != g.sender) {
1158 self.sems
1159 .insert((*sem, r), SemValue::Valid.as_u32() as usize);
1160 }
1161 Ok(true)
1162 }
1163 ChoreoOp::SemSet { sem, value } => {
1164 self.sems.insert((*sem, site), value.as_u32() as usize);
1165 Ok(true)
1166 }
1167 ChoreoOp::SemInc {
1168 sem,
1169 at: target,
1170 by,
1171 } => {
1172 *self.sems.entry((*sem, *target)).or_insert(0) += by;
1173 Ok(true)
1174 }
1175 ChoreoOp::SemWait { sem, until } => {
1176 let v = self.sems.get(&(*sem, site)).copied().unwrap_or(0);
1177 Ok(match until {
1178 SemCond::Equals(n) => v == *n,
1179 SemCond::AtLeast(n) => v >= *n,
1180 })
1181 }
1182 ChoreoOp::AcquireAcc { tiles } => {
1183 if *tiles > self.p.acc_tiles_used.max(1) {
1184 return Err(ChoreoFault::AccOverflow {
1185 requested: *tiles,
1186 available: self.p.acc_tiles_used,
1187 });
1188 }
1189 let c = &mut self.programs[pi];
1190 if c.acc.is_some() {
1191 return Err(ChoreoFault::AccProtocol { at });
1192 }
1193 let n = self.t * self.t;
1194 let acc_float = self
1197 .p
1198 .plan_of_stage(stage)
1199 .map(|pl| pl.acc == AccPolicy::F32)
1200 .unwrap_or(false);
1201 c.acc = Some(
1202 (0..*tiles)
1203 .map(|_| {
1204 if acc_float {
1205 AccData::F32(vec![0.0; n])
1206 } else {
1207 AccData::Int(vec![0; n])
1208 }
1209 })
1210 .collect(),
1211 );
1212 c.acc_computed = false;
1213 c.acc_folded.clear();
1214 Ok(true)
1215 }
1216 ChoreoOp::ReleaseAcc => {
1217 let c = &mut self.programs[pi];
1218 if c.acc.is_none() {
1219 return Err(ChoreoFault::AccProtocol { at });
1220 }
1221 c.acc = None;
1222 Ok(true)
1223 }
1224 ChoreoOp::Compute {
1225 op,
1226 ins,
1227 acc_reload,
1228 } => {
1229 let stepped = self.compute(pi, site, engine, at, stage, op, ins, *acc_reload)?;
1230 if stepped && !self.dry {
1231 self.note_acc(pi, stage);
1232 }
1233 Ok(stepped)
1234 }
1235 ChoreoOp::PackAcc {
1236 to,
1237 tiles,
1238 convert,
1239 accumulate,
1240 } => {
1241 let acc = match self.programs[pi].acc.as_ref() {
1242 Some(a) => a.clone(),
1243 None => return Err(ChoreoFault::AccProtocol { at }),
1244 };
1245 if !self.programs[pi].acc_computed {
1246 return Err(ChoreoFault::AccProtocol { at });
1247 }
1248 if acc.len() < *tiles {
1249 return Err(ChoreoFault::AccOverflow {
1250 requested: *tiles,
1251 available: acc.len(),
1252 });
1253 }
1254 let plan_acc = self
1255 .p
1256 .plan_of_stage(stage)
1257 .map(|pl| pl.acc)
1258 .unwrap_or(AccPolicy::I64);
1259 let page: Page = if self.dry {
1260 vec![TileData::Empty; *tiles]
1261 } else {
1262 acc.iter()
1263 .take(*tiles)
1264 .map(|a| match a {
1265 AccData::Int(v) => {
1266 let vals: Vec<i64> = v
1267 .iter()
1268 .map(|x| {
1269 if plan_acc == AccPolicy::I32 {
1270 trunc_signed(*x, 32)
1271 } else {
1272 *x
1273 }
1274 })
1275 .collect();
1276 if precision_is_int(*convert) {
1277 TileData::Int(vals)
1278 } else {
1279 TileData::Float(
1280 vals.iter()
1281 .map(|x| round_to(*convert, *x as f64))
1282 .collect(),
1283 )
1284 }
1285 }
1286 AccData::F32(v) => {
1287 if precision_is_int(*convert) {
1288 TileData::Int(v.iter().map(|x| *x as i64).collect())
1289 } else {
1290 TileData::Float(
1291 v.iter().map(|x| round_to(*convert, *x as f64)).collect(),
1292 )
1293 }
1294 }
1295 })
1296 .collect()
1297 };
1298 let f = &self.fifos[to.idx()];
1299 if f.pending.len() >= f.reserved {
1300 return Err(ChoreoFault::BufferOverflow { buf: *to, at });
1301 }
1302 let page = if *accumulate && !self.dry {
1303 let store = &self.fifos[self.storage(*to).idx()].slots;
1306 let slot = (f.wr + f.pending.len()) % store.len().max(1);
1307 let base = store.get(slot).cloned().unwrap_or_default();
1308 add_pages(&base, &page)
1309 } else {
1310 page
1311 };
1312 self.fifos[to.idx()].pending.push_back(page);
1313 self.touch(*to);
1314 Ok(true)
1315 }
1316 ChoreoOp::Barrier { .. } => Err(ChoreoFault::Unsupported(
1317 "barrier groups are not lowered by any pass yet".into(),
1318 )),
1319 ChoreoOp::Loop { .. } => unreachable!("loops are flattened"),
1320 }
1321 }
1322
1323 fn front_page(&self, buf: BufferId, at: (Site, Engine, usize)) -> Result<Page, ChoreoFault> {
1324 let f = &self.fifos[buf.idx()];
1325 if f.visible == 0 {
1326 return Err(if f.queue.is_empty() {
1327 ChoreoFault::Underflow { buf, at }
1328 } else {
1329 ChoreoFault::ReadBeforeWait { buf, at }
1330 });
1331 }
1332 f.queue
1333 .front()
1334 .cloned()
1335 .ok_or(ChoreoFault::Underflow { buf, at })
1336 }
1337
1338 #[allow(clippy::too_many_arguments)]
1339 fn compute(
1340 &mut self,
1341 pi: usize,
1342 site: Site,
1343 _engine: Engine,
1344 at: (Site, Engine, usize),
1345 stage: StageId,
1346 op: &ComputeOp,
1347 ins: &[BufferId],
1348 acc_reload: Option<BufferId>,
1349 ) -> Result<bool, ChoreoFault> {
1350 let t = self.t;
1351 let n = t * t;
1352 if self.programs[pi].acc.is_none() {
1353 return Err(ChoreoFault::AccProtocol { at });
1354 }
1355 let mut folded: Vec<_> = Vec::new();
1361 for b in ins {
1362 let key = (b.idx(), self.fifos[b.idx()].popped);
1363 if folded.contains(&key) {
1364 continue;
1365 }
1366 folded.push(key);
1367 if !self.programs[pi].acc_folded.insert(key) {
1368 return Err(ChoreoFault::AccProtocol { at });
1369 }
1370 }
1371 self.programs[pi].acc_computed = true;
1372 if let ComputeOp::Generic { out, .. } = op {
1379 if out.indirect.is_some() {
1380 return Err(ChoreoFault::Unsupported(INDIRECT_MAP_DEFERRED.into()));
1381 }
1382 }
1383 if self.dry {
1384 for b in ins {
1387 self.front_page(*b, at)?;
1388 }
1389 if let Some(interm) = acc_reload {
1390 let page = self.front_page(interm, at)?;
1391 let acc = self.programs[pi].acc.as_ref().unwrap();
1392 if page.len() < acc.len() {
1393 return Err(ChoreoFault::Unsupported(format!(
1394 "reload page of {} tiles for {} accumulators",
1395 page.len(),
1396 acc.len()
1397 )));
1398 }
1399 }
1400 return Ok(true);
1401 }
1402 if let Some(interm) = acc_reload {
1403 let page = self.front_page(interm, at)?;
1404 let acc = self.programs[pi].acc.as_mut().unwrap();
1405 for (a, tile) in acc.iter_mut().zip(page.iter()) {
1406 *a = match (tile, &*a) {
1407 (TileData::Int(v), AccData::Int(_)) => AccData::Int(v.clone()),
1408 (TileData::Float(v), AccData::F32(_)) => {
1409 AccData::F32(v.iter().map(|x| *x as f32).collect())
1410 }
1411 (TileData::Int(v), AccData::F32(_)) => {
1412 AccData::F32(v.iter().map(|x| *x as f32).collect())
1413 }
1414 (TileData::Float(v), AccData::Int(_)) => {
1415 AccData::Int(v.iter().map(|x| *x as i64).collect())
1416 }
1417 (TileData::Empty, a) => a.clone(),
1418 };
1419 }
1420 }
1421 match op {
1422 ComputeOp::MatmulTiles {
1423 block,
1424 sub_blocks,
1425 operand,
1426 acc,
1427 ..
1428 } => {
1429 let a_page = self.front_page(ins[0], at)?;
1430 let b_page = self.front_page(ins[1], at)?;
1431 let env = &self.programs[pi].env;
1432 let sh = env.get(&LV_SUB_H).copied().unwrap_or(0);
1433 let sw = env.get(&LV_SUB_W).copied().unwrap_or(0);
1434 let (osh, osw, ibw) = (block.osh, block.osw, block.ibw);
1435 let per_site_n = sub_blocks.1 * osw;
1436 let bits = precision_int_bits(*operand).unwrap_or(64);
1437 let accs = self.programs[pi].acc.as_mut().unwrap();
1438 if accs.len() < osh * osw {
1439 return Err(ChoreoFault::AccOverflow {
1440 requested: osh * osw,
1441 available: accs.len(),
1442 });
1443 }
1444 for i in 0..osh {
1445 for j in 0..osw {
1446 for kk in 0..ibw {
1447 let dst = &mut accs[i * osw + j];
1448 let a_tile = a_page
1449 .get((sh * osh + i) * ibw + kk)
1450 .ok_or(ChoreoFault::Unsupported("A page shape".into()))?;
1451 let b_tile = b_page
1452 .get(kk * per_site_n + (sw * osw + j))
1453 .ok_or(ChoreoFault::Unsupported("B page shape".into()))?;
1454 match (dst, a_tile, b_tile) {
1455 (AccData::Int(d), TileData::Int(a), TileData::Int(b)) => {
1456 for r in 0..t {
1457 for c in 0..t {
1458 let mut s = d[r * t + c];
1459 for x in 0..t {
1460 s = s.wrapping_add(
1461 trunc_signed(a[r * t + x], bits).wrapping_mul(
1462 trunc_signed(b[x * t + c], bits),
1463 ),
1464 );
1465 }
1466 d[r * t + c] = s;
1467 }
1468 }
1469 }
1470 (AccData::F32(d), TileData::Float(a), TileData::Float(b)) => {
1471 for r in 0..t {
1472 for c in 0..t {
1473 let mut s = d[r * t + c];
1474 for x in 0..t {
1475 s += a[r * t + x] as f32 * b[x * t + c] as f32;
1476 }
1477 d[r * t + c] = s;
1478 }
1479 }
1480 }
1481 _ => {
1482 return Err(ChoreoFault::Unsupported(format!(
1483 "operand/accumulator mix for {acc:?}"
1484 )))
1485 }
1486 }
1487 }
1488 }
1489 }
1490 Ok(true)
1491 }
1492 ComputeOp::Requant { m, s } => {
1493 let page = self.front_page(ins[0], at)?;
1494 let accs = self.programs[pi].acc.as_mut().unwrap();
1495 for (k, dst) in accs.iter_mut().enumerate() {
1496 let Some(TileData::Int(v)) = page.get(k) else {
1497 return Err(ChoreoFault::Unsupported(
1498 "requant needs integer tiles".into(),
1499 ));
1500 };
1501 *dst = AccData::Int(v.iter().map(|x| m.wrapping_mul(*x) >> s).collect());
1502 }
1503 Ok(true)
1504 }
1505 ComputeOp::Bias { .. } => {
1506 let page = self.front_page(ins[0], at)?;
1507 let bias = self.front_page(ins[1], at)?;
1508 let accs = self.programs[pi].acc.as_mut().unwrap();
1509 for (k, dst) in accs.iter_mut().enumerate() {
1510 let (Some(TileData::Int(v)), Some(TileData::Int(b))) =
1511 (page.get(k), bias.get(k % bias.len().max(1)))
1512 else {
1513 return Err(ChoreoFault::Unsupported("bias needs integer tiles".into()));
1514 };
1515 let mut out = vec![0i64; n];
1516 for r in 0..t {
1517 for c in 0..t {
1518 out[r * t + c] = v[r * t + c].wrapping_add(b[c]);
1519 }
1520 }
1521 *dst = AccData::Int(out);
1522 }
1523 Ok(true)
1524 }
1525 ComputeOp::Relu => {
1526 let page = self.front_page(ins[0], at)?;
1527 let accs = self.programs[pi].acc.as_mut().unwrap();
1528 for (k, dst) in accs.iter_mut().enumerate() {
1529 match page.get(k) {
1530 Some(TileData::Int(v)) => {
1531 *dst = AccData::Int(v.iter().map(|x| (*x).max(0)).collect())
1532 }
1533 Some(TileData::Float(v)) => {
1534 *dst = AccData::F32(v.iter().map(|x| x.max(0.0) as f32).collect())
1535 }
1536 Some(TileData::Empty) | None => {
1537 return Err(ChoreoFault::Unsupported("relu page shape".into()))
1538 }
1539 }
1540 }
1541 Ok(true)
1542 }
1543 ComputeOp::Generic {
1554 iters,
1555 ins: maps,
1556 out,
1557 body,
1558 } => {
1559 let (row_ax, col_ax, k_ax) = match iters.as_slice() {
1560 [r, c] => (*r, *c, None),
1561 [r, c, k] => (*r, *c, Some(*k)),
1562 _ => return Err(ChoreoFault::Unsupported("a Generic iteration space that is not (rows, cols) or (rows, cols, contraction)".into())),
1563 };
1564 let k_var = k_ax
1565 .map(|k| k.var)
1566 .unwrap_or(crate::spatial::passes::LV_GEN_K);
1567 if maps.len() != ins.len() {
1568 return Err(ChoreoFault::Unsupported(
1569 "a Generic whose access maps do not match its operands".into(),
1570 ));
1571 }
1572 for it in [row_ax, col_ax] {
1573 if matches!(it.kind, IterKind::Reduce { .. }) && out.varies_along(it.var) {
1574 return Err(ChoreoFault::Unsupported(
1575 "a Generic whose output varies along a reduced axis".into(),
1576 ));
1577 }
1578 }
1579 if body.max_operand().is_some_and(|i| i >= ins.len()) {
1580 return Err(ChoreoFault::Unsupported(
1581 "a Generic body naming an absent operand".into(),
1582 ));
1583 }
1584 let pages: Vec<Page> = ins
1585 .iter()
1586 .map(|b| self.front_page(*b, at))
1587 .collect::<Result<_, _>>()?;
1588 let read_flat = |page: &Page, off: i64| -> f64 {
1595 let width = (page.len() * t) as i64;
1596 if off < 0 || width == 0 || off >= t as i64 * width {
1597 return 0.0;
1598 }
1599 let (r, c) = ((off / width) as usize, (off % width) as usize);
1600 let (tile, e) = (c / t, r * t + c % t);
1601 match page.get(tile) {
1602 Some(TileData::Float(v)) => v.get(e).copied().unwrap_or(0.0),
1603 Some(TileData::Int(v)) => v.get(e).copied().unwrap_or(0) as f64,
1604 _ => 0.0,
1605 }
1606 };
1607 fn holds(
1608 p: &Pred,
1609 operand: &dyn Fn(usize) -> f64,
1610 axis: &dyn Fn(LoopVar) -> usize,
1611 ) -> bool {
1612 let bound = match p.rhs() {
1613 PredRhs::Const(k) => k as f64,
1614 PredRhs::Operand(i) => operand(i),
1615 };
1616 let x = axis(p.var()) as f64;
1617 match p {
1618 Pred::AxisLt(..) => x < bound,
1619 Pred::AxisGe(..) => x >= bound,
1620 }
1621 }
1622 fn apply(
1623 b: &TensorBody,
1624 operand: &dyn Fn(usize) -> f64,
1625 axis: &dyn Fn(LoopVar) -> usize,
1626 ) -> f64 {
1627 match b {
1628 TensorBody::Operand(i) => operand(*i),
1629 TensorBody::Const(bits) => f64::from_bits(*bits),
1630 TensorBody::Add(x, y) => apply(x, operand, axis) + apply(y, operand, axis),
1631 TensorBody::Sub(x, y) => apply(x, operand, axis) - apply(y, operand, axis),
1632 TensorBody::Mul(x, y) => apply(x, operand, axis) * apply(y, operand, axis),
1633 TensorBody::Min(x, y) => apply(x, operand, axis).min(apply(y, operand, axis)),
1634 TensorBody::Max(x, y) => apply(x, operand, axis).max(apply(y, operand, axis)),
1635 TensorBody::FloorDiv(x, y) => (apply(x, operand, axis) / apply(y, operand, axis)).floor(),
1636 TensorBody::Shr { arg, by } => (apply(arg, operand, axis) / 2f64.powi(*by as i32)).floor(),
1637 TensorBody::Unary { kind, arg } => {
1638 let v = apply(arg, operand, axis);
1639 match kind {
1640 SfpuKind::Exp => v.exp(),
1641 SfpuKind::Recip => 1.0 / v,
1642 SfpuKind::Rsqrt { eps_nano, over } => {
1643 1.0 / (v / (*over).max(1) as f64 + *eps_nano as f64 * 1e-9)
1644 .sqrt()
1645 }
1646 SfpuKind::Silu => v / (1.0 + (-v).exp()),
1647 SfpuKind::Relu => v.max(0.0),
1648 SfpuKind::Ln => v.ln(),
1649 }
1650 }
1651 TensorBody::Select {
1653 cond,
1654 then,
1655 otherwise,
1656 } => {
1657 if holds(cond, operand, axis) {
1658 apply(then, operand, axis)
1659 } else {
1660 apply(otherwise, operand, axis)
1661 }
1662 }
1663 }
1664 }
1665 let fault: std::cell::Cell<Option<(f64, usize)>> = std::cell::Cell::new(None);
1672 let offset = |i: usize, r: usize, c: usize, x: usize| -> i64 {
1673 let affine = maps[i].offset_at_k(row_ax.var, r, col_ax.var, c, k_var, x);
1674 let Some(ind) = maps[i].indirect else {
1675 return affine;
1676 };
1677 let v = read_flat(
1678 &pages[ind.operand],
1679 maps[ind.operand].offset_at_k(row_ax.var, r, col_ax.var, c, k_var, x),
1680 );
1681 if v.fract() != 0.0 || v < 0.0 || v >= ind.modulus as f64 {
1682 if fault.get().is_none() {
1683 fault.set(Some((v, ind.modulus)));
1684 }
1685 return affine;
1686 }
1687 affine + v as i64 * ind.stride
1688 };
1689 let value_at = |r: usize, c: usize, x: usize| -> f64 {
1692 let operand = |i: usize| read_flat(&pages[i], offset(i, r, c, x));
1693 let axis = |v: LoopVar| {
1694 if v == row_ax.var {
1695 r
1696 } else if v == col_ax.var {
1697 c
1698 } else if k_ax.is_some() && v == k_var {
1699 x
1700 } else {
1701 0
1702 }
1703 };
1704 apply(body, &operand, &axis)
1705 };
1706 let value = |r: usize, c: usize| -> f64 {
1707 match k_ax {
1708 None => value_at(r, c, 0),
1709 Some(k) => (0..k.extent).map(|x| value_at(r, c, x)).sum(),
1710 }
1711 };
1712 if matches!(row_ax.kind, IterKind::Parallel) && row_ax.extent > t {
1716 return Err(ChoreoFault::Unsupported(format!(
1717 "a Generic with {} parallel rows on a {t}-row tile — a page holds {t} rows, so rows past it would overwrite earlier ones",
1718 row_ax.extent
1719 )));
1720 }
1721 let out_tiles = if matches!(col_ax.kind, IterKind::Reduce { .. }) {
1722 1
1723 } else {
1724 col_ax.extent.div_ceil(t)
1725 };
1726 if ins
1733 .iter()
1734 .all(|b| precision_int_bits(self.p.buffer(*b).dtype).is_some())
1735 && body.integral()
1736 {
1737 fn apply_int(
1738 b: &TensorBody,
1739 operand: &dyn Fn(usize) -> i64,
1740 axis: &dyn Fn(LoopVar) -> usize,
1741 ) -> Result<i64, &'static str> {
1742 let two = |x: &TensorBody, y: &TensorBody| -> Result<(i64, i64), &'static str> {
1743 Ok((apply_int(x, operand, axis)?, apply_int(y, operand, axis)?))
1744 };
1745 Ok(match b {
1746 TensorBody::Operand(i) => operand(*i),
1747 TensorBody::Const(bits) => f64::from_bits(*bits) as i64,
1748 TensorBody::Add(x, y) => {
1749 let (a, b) = two(x, y)?;
1750 a.checked_add(b).ok_or("an integer sum past ±2⁶³")?
1751 }
1752 TensorBody::Sub(x, y) => {
1753 let (a, b) = two(x, y)?;
1754 a.checked_sub(b).ok_or("an integer difference past ±2⁶³")?
1755 }
1756 TensorBody::Mul(x, y) => {
1757 let (a, b) = two(x, y)?;
1758 a.checked_mul(b).ok_or("an integer product past ±2⁶³")?
1759 }
1760 TensorBody::Min(x, y) => {
1761 let (a, b) = two(x, y)?;
1762 a.min(b)
1763 }
1764 TensorBody::Max(x, y) => {
1765 let (a, b) = two(x, y)?;
1766 a.max(b)
1767 }
1768 TensorBody::FloorDiv(x, y) => {
1769 let (a, d) = two(x, y)?;
1770 if d == 0 {
1771 return Err("an integer division by zero");
1772 }
1773 let q = a.checked_div(d).ok_or("an integer quotient past ±2⁶³")?;
1774 if a % d != 0 && ((a < 0) != (d < 0)) {
1775 q - 1
1776 } else {
1777 q
1778 }
1779 }
1780 TensorBody::Shr { arg, by } => apply_int(arg, operand, axis)? >> by,
1781 TensorBody::Unary { .. } => return Err("a transcendental in an integer body"),
1782 TensorBody::Select {
1783 cond,
1784 then,
1785 otherwise,
1786 } => {
1787 let as_f64 = |i: usize| operand(i) as f64;
1788 if holds(cond, &as_f64, axis) {
1789 apply_int(then, operand, axis)?
1790 } else {
1791 apply_int(otherwise, operand, axis)?
1792 }
1793 }
1794 })
1795 }
1796 let read_int = |page: &Page, off: i64| -> i64 {
1797 let width = (page.len() * t) as i64;
1798 if off < 0 || width == 0 || off >= t as i64 * width {
1799 return 0;
1800 }
1801 let (r, c) = ((off / width) as usize, (off % width) as usize);
1802 let (tile, e) = (c / t, r * t + c % t);
1803 match page.get(tile) {
1804 Some(TileData::Int(v)) => v.get(e).copied().unwrap_or(0),
1805 Some(TileData::Float(v)) => v.get(e).copied().unwrap_or(0.0) as i64,
1806 _ => 0,
1807 }
1808 };
1809 let value_at = |r: usize, c: usize, x: usize| -> Result<i64, &'static str> {
1810 let operand = |i: usize| read_int(&pages[i], offset(i, r, c, x));
1811 let axis = |v: LoopVar| {
1812 if v == row_ax.var {
1813 r
1814 } else if v == col_ax.var {
1815 c
1816 } else if k_ax.is_some() && v == k_var {
1817 x
1818 } else {
1819 0
1820 }
1821 };
1822 apply_int(body, &operand, &axis)
1823 };
1824 let kept: std::cell::Cell<Option<(i64, i64)>> = std::cell::Cell::new(None);
1826 let keep = |v: i64| kept.set(Some(widen(kept.get(), v)));
1827 let value = |r: usize, c: usize| -> Result<i64, &'static str> {
1828 match k_ax {
1829 None => value_at(r, c, 0),
1830 Some(k) => (0..k.extent).try_fold(0i64, |sum, x| {
1831 let sum = sum
1832 .checked_add(value_at(r, c, x)?)
1833 .ok_or("an integer contraction past ±2⁶³")?;
1834 keep(sum);
1835 Ok(sum)
1836 }),
1837 }
1838 };
1839 struct IntFold {
1840 pool: ReducePool,
1841 acc: i64,
1842 idx: usize,
1843 seeded: bool,
1844 held: Option<(i64, i64)>,
1845 }
1846 impl IntFold {
1847 fn new(pool: ReducePool) -> IntFold {
1848 IntFold {
1849 pool,
1850 acc: 0,
1851 idx: 0,
1852 seeded: false,
1853 held: None,
1854 }
1855 }
1856 fn step(&mut self, i: usize, v: i64) -> Result<(), &'static str> {
1857 match self.pool {
1858 ReducePool::Sum => {
1859 self.acc = self
1860 .acc
1861 .checked_add(v)
1862 .ok_or("an integer row sum past ±2⁶³")?
1863 }
1864 ReducePool::Max => {
1865 self.acc = if self.seeded { self.acc.max(v) } else { v }
1866 }
1867 ReducePool::ArgMax => {
1868 if !self.seeded || v > self.acc {
1869 self.acc = v;
1870 self.idx = i;
1871 }
1872 }
1873 }
1874 self.seeded = true;
1875 self.held = Some(widen(self.held, self.acc));
1876 Ok(())
1877 }
1878 fn result(&self) -> i64 {
1879 match self.pool {
1880 ReducePool::Sum | ReducePool::Max => self.acc,
1881 ReducePool::ArgMax => self.idx as i64,
1882 }
1883 }
1884 }
1885 let kept_fold = |f: &IntFold| {
1886 if let Some((lo, hi)) = f.held {
1887 keep(lo);
1888 keep(hi);
1889 }
1890 };
1891 let fail = |what: &'static str| ChoreoFault::IntegerArithmetic { at, what };
1892 let mut plane = vec![vec![0i64; n]; out_tiles.max(1)];
1893 match (row_ax.kind, col_ax.kind) {
1894 (IterKind::Parallel, IterKind::Parallel) => {
1895 for r in 0..row_ax.extent {
1896 for c in 0..col_ax.extent {
1897 plane[c / t][(r % t) * t + (c % t)] = value(r, c).map_err(fail)?;
1898 }
1899 }
1900 }
1901 (IterKind::Parallel, IterKind::Reduce { pool }) => {
1902 for r in 0..row_ax.extent {
1903 let mut fold = IntFold::new(pool);
1904 for c in 0..col_ax.extent {
1905 fold.step(c, value(r, c).map_err(fail)?).map_err(fail)?;
1906 }
1907 kept_fold(&fold);
1908 let v = fold.result();
1909 for cc in 0..t {
1910 plane[0][(r % t) * t + cc] = v;
1911 }
1912 }
1913 }
1914 (IterKind::Reduce { pool }, IterKind::Parallel) => {
1915 for c in 0..col_ax.extent {
1916 let mut fold = IntFold::new(pool);
1917 for r in 0..row_ax.extent {
1918 fold.step(r, value(r, c).map_err(fail)?).map_err(fail)?;
1919 }
1920 kept_fold(&fold);
1921 let v = fold.result();
1922 for rr in 0..t {
1923 plane[c / t][rr * t + (c % t)] = v;
1924 }
1925 }
1926 }
1927 (IterKind::Reduce { pool: row_pool }, IterKind::Reduce { pool: col_pool }) => {
1928 let mut outer = IntFold::new(row_pool);
1929 for r in 0..row_ax.extent {
1930 let mut inner = IntFold::new(col_pool);
1931 for c in 0..col_ax.extent {
1932 inner.step(c, value(r, c).map_err(fail)?).map_err(fail)?;
1933 }
1934 kept_fold(&inner);
1935 outer.step(r, inner.result()).map_err(fail)?;
1936 }
1937 kept_fold(&outer);
1938 let v = outer.result();
1939 for e in plane[0].iter_mut() {
1940 *e = v;
1941 }
1942 }
1943 }
1944 if let Some((index, extent)) = fault.get() {
1945 return Err(ChoreoFault::GatherOutOfRange { at, index, extent });
1946 }
1947 fn note_nodes(
1950 b: &TensorBody,
1951 operand: &dyn Fn(usize) -> i64,
1952 axis: &dyn Fn(LoopVar) -> usize,
1953 next: &mut usize,
1954 seen: &mut [Option<(i64, i64)>],
1955 ) -> Option<i64> {
1956 let two = |x: &TensorBody, y: &TensorBody, next: &mut usize, seen: &mut [Option<(i64, i64)>]| {
1957 let a = note_nodes(x, operand, axis, next, seen);
1958 let c = note_nodes(y, operand, axis, next, seen);
1959 a.zip(c)
1960 };
1961 let v = match b {
1962 TensorBody::Operand(i) => Some(operand(*i)),
1963 TensorBody::Const(bits) => Some(f64::from_bits(*bits) as i64),
1964 TensorBody::Add(x, y) => two(x, y, next, seen).and_then(|(a, c)| a.checked_add(c)),
1965 TensorBody::Sub(x, y) => two(x, y, next, seen).and_then(|(a, c)| a.checked_sub(c)),
1966 TensorBody::Mul(x, y) => two(x, y, next, seen).and_then(|(a, c)| a.checked_mul(c)),
1967 TensorBody::Min(x, y) => two(x, y, next, seen).map(|(a, c)| a.min(c)),
1968 TensorBody::Max(x, y) => two(x, y, next, seen).map(|(a, c)| a.max(c)),
1969 TensorBody::FloorDiv(x, y) => two(x, y, next, seen).and_then(|(a, d)| {
1970 let q = a.checked_div(d)?;
1971 Some(if a % d != 0 && ((a < 0) != (d < 0)) { q - 1 } else { q })
1972 }),
1973 TensorBody::Shr { arg, by } => note_nodes(arg, operand, axis, next, seen).map(|a| a >> by),
1974 TensorBody::Unary { arg, .. } => {
1975 note_nodes(arg, operand, axis, next, seen);
1976 None
1977 }
1978 TensorBody::Select { cond, then, otherwise } => {
1979 let t = note_nodes(then, operand, axis, next, seen);
1980 let o = note_nodes(otherwise, operand, axis, next, seen);
1981 let as_f64 = |i: usize| operand(i) as f64;
1982 if holds(cond, &as_f64, axis) {
1983 t
1984 } else {
1985 o
1986 }
1987 }
1988 };
1989 let i = *next;
1990 *next += 1;
1991 if let Some(v) = v {
1992 seen[i] = Some(widen(seen[i], v));
1993 }
1994 v
1995 }
1996 let mut seen = self.nodes.get(&stage).cloned().unwrap_or_else(|| vec![None; body.post_order().len()]);
1997 for r in 0..row_ax.extent {
1998 for c in 0..col_ax.extent {
1999 for x in 0..k_ax.map_or(1, |k| k.extent) {
2000 let operand = |i: usize| read_int(&pages[i], offset(i, r, c, x));
2001 let axis = |v: LoopVar| {
2002 if v == row_ax.var {
2003 r
2004 } else if v == col_ax.var {
2005 c
2006 } else if k_ax.is_some() && v == k_var {
2007 x
2008 } else {
2009 0
2010 }
2011 };
2012 let mut next = 0;
2013 note_nodes(body, &operand, &axis, &mut next, &mut seen);
2014 }
2015 }
2016 }
2017 self.nodes.insert(stage, seen);
2018 if let Some((lo, hi)) = kept.get() {
2019 let h = widen(Some(widen(self.stage_held.get(&stage).copied(), lo)), hi);
2020 self.stage_held.insert(stage, h);
2021 }
2022 let accs = self.programs[pi].acc.as_mut().unwrap();
2023 for (k, dst) in accs.iter_mut().enumerate().take(plane.len()) {
2024 *dst = AccData::Int(plane[k].clone());
2025 }
2026 return Ok(true);
2027 }
2028 struct Fold {
2033 pool: ReducePool,
2034 acc: f64,
2035 idx: usize,
2036 seeded: bool,
2037 }
2038 impl Fold {
2039 fn new(pool: ReducePool) -> Fold {
2040 Fold {
2041 pool,
2042 acc: match pool {
2043 ReducePool::Sum => 0.0,
2044 ReducePool::Max | ReducePool::ArgMax => f64::NEG_INFINITY,
2045 },
2046 idx: 0,
2047 seeded: false,
2048 }
2049 }
2050 fn step(&mut self, i: usize, v: f64) {
2051 match self.pool {
2052 ReducePool::Sum => self.acc += v,
2053 ReducePool::Max => self.acc = self.acc.max(v),
2054 ReducePool::ArgMax => {
2055 if !self.seeded || v > self.acc {
2056 self.acc = v;
2057 self.idx = i;
2058 self.seeded = true;
2059 }
2060 }
2061 }
2062 }
2063 fn result(&self) -> f64 {
2064 match self.pool {
2065 ReducePool::Sum | ReducePool::Max => self.acc,
2066 ReducePool::ArgMax => self.idx as f64,
2067 }
2068 }
2069 }
2070 let mut plane = vec![vec![0f64; n]; out_tiles.max(1)];
2076 match (row_ax.kind, col_ax.kind) {
2077 (IterKind::Parallel, IterKind::Parallel) => {
2078 for r in 0..row_ax.extent {
2079 for c in 0..col_ax.extent {
2080 plane[c / t][(r % t) * t + (c % t)] = value(r, c);
2081 }
2082 }
2083 }
2084 (IterKind::Parallel, IterKind::Reduce { pool }) => {
2085 for r in 0..row_ax.extent {
2086 let mut fold = Fold::new(pool);
2087 for c in 0..col_ax.extent {
2088 fold.step(c, value(r, c));
2089 }
2090 let v = fold.result();
2091 for cc in 0..t {
2092 plane[0][(r % t) * t + cc] = v;
2093 }
2094 }
2095 }
2096 (IterKind::Reduce { pool }, IterKind::Parallel) => {
2097 for c in 0..col_ax.extent {
2098 let mut fold = Fold::new(pool);
2099 for r in 0..row_ax.extent {
2100 fold.step(r, value(r, c));
2101 }
2102 let v = fold.result();
2103 for rr in 0..t {
2104 plane[c / t][rr * t + (c % t)] = v;
2105 }
2106 }
2107 }
2108 (IterKind::Reduce { pool: row_pool }, IterKind::Reduce { pool: col_pool }) => {
2109 let mut outer = Fold::new(row_pool);
2111 for r in 0..row_ax.extent {
2112 let mut inner = Fold::new(col_pool);
2113 for c in 0..col_ax.extent {
2114 inner.step(c, value(r, c));
2115 }
2116 outer.step(r, inner.result());
2117 }
2118 let v = outer.result();
2119 for e in plane[0].iter_mut() {
2120 *e = v;
2121 }
2122 }
2123 }
2124 if let Some((index, extent)) = fault.get() {
2125 return Err(ChoreoFault::GatherOutOfRange { at, index, extent });
2126 }
2127 let integer = ins
2134 .iter()
2135 .all(|b| precision_int_bits(self.p.buffer(*b).dtype).is_some())
2136 || [row_ax, col_ax].iter().any(|a| {
2137 matches!(
2138 a.kind,
2139 IterKind::Reduce {
2140 pool: ReducePool::ArgMax
2141 }
2142 )
2143 });
2144 let accs = self.programs[pi].acc.as_mut().unwrap();
2145 for (k, dst) in accs.iter_mut().enumerate().take(plane.len()) {
2146 *dst = if integer {
2147 AccData::Int(plane[k].iter().map(|v| *v as i64).collect())
2148 } else {
2149 AccData::F32(plane[k].iter().map(|v| *v as f32).collect())
2150 };
2151 }
2152 Ok(true)
2153 }
2154 ComputeOp::Scalar(body) => {
2155 let pages: Vec<Page> = ins
2156 .iter()
2157 .map(|b| self.front_page(*b, at))
2158 .collect::<Result<_, _>>()?;
2159 let df = DecisionFabric {
2160 n_in: body.n_in,
2161 cells: body.cells.clone(),
2162 result: body.result,
2163 escaped_first_return: false,
2164 };
2165 let accs = self.programs[pi].acc.as_mut().unwrap();
2166 let mut heap: Vec<i64> = Vec::new();
2167 for (k, dst) in accs.iter_mut().enumerate() {
2168 let mut out = vec![0i64; n];
2169 for e in 0..n {
2170 let inputs: Vec<i64> = pages
2171 .iter()
2172 .map(|p| match p.get(k) {
2173 Some(TileData::Int(v)) => v[e],
2174 Some(TileData::Float(v)) => v[e] as i64,
2175 Some(TileData::Empty) | None => 0,
2176 })
2177 .collect();
2178 out[e] = eval_decision_fabric_heap(&df, &inputs, &mut heap);
2179 }
2180 *dst = AccData::Int(out);
2181 }
2182 let _ = site;
2183 Ok(true)
2184 }
2185 }
2186 }
2187
2188 fn order(&self, sched: Scheduler, round: u64) -> Vec<usize> {
2189 let n = self.programs.len();
2190 let mut idx: Vec<usize> = (0..n).collect();
2191 match sched {
2192 Scheduler::RoundRobin => {}
2193 Scheduler::Reverse => idx.reverse(),
2194 Scheduler::Seeded(seed) => {
2195 let mut x = seed ^ round.wrapping_mul(0x9E3779B97F4A7C15);
2196 for i in (1..n).rev() {
2197 x ^= x << 13;
2198 x ^= x >> 7;
2199 x ^= x << 17;
2200 let j = (x % (i as u64 + 1)) as usize;
2201 idx.swap(i, j);
2202 }
2203 }
2204 Scheduler::MovementFirst => {
2205 idx.sort_by_key(|i| matches!(self.programs[*i].engine, Engine::Compute))
2206 }
2207 Scheduler::ComputeFirst => {
2208 idx.sort_by_key(|i| !matches!(self.programs[*i].engine, Engine::Compute))
2209 }
2210 }
2211 idx
2212 }
2213
2214 fn run(&mut self, sched: Scheduler) -> Result<(), ChoreoFault> {
2215 let mut round = 0u64;
2216 loop {
2217 let order = self.order(sched, round);
2218 let mut progressed = false;
2219 let mut all_done = true;
2220 for pi in order {
2221 if self.programs[pi].done {
2222 continue;
2223 }
2224 all_done = false;
2225 let mut budget = 64;
2227 while budget > 0 && !self.programs[pi].done {
2228 if self.step(pi)? {
2229 progressed = true;
2230 self.steps += 1;
2231 } else {
2232 break;
2233 }
2234 budget -= 1;
2235 }
2236 }
2237 if all_done {
2238 return Ok(());
2239 }
2240 if !progressed {
2241 let blocked_at = self
2242 .programs
2243 .iter()
2244 .filter(|c| !c.done)
2245 .map(|c| {
2246 let op = match c.code.get(c.pc) {
2247 Some(Instr::Op(op, _)) => op.clone(),
2248 _ => ChoreoOp::ReadBarrier,
2249 };
2250 (c.site, c.engine, c.pc, op)
2251 })
2252 .collect();
2253 return Err(ChoreoFault::Deadlock {
2254 runnable: Vec::new(),
2255 blocked_at,
2256 });
2257 }
2258 round += 1;
2259 }
2260 }
2261
2262 fn finish(self) -> Result<(Outputs, Trace), ChoreoFault> {
2263 self.finish_with(false)
2264 }
2265
2266 fn finish_with(mut self, every: bool) -> Result<(Outputs, Trace), ChoreoFault> {
2269 for (i, f) in self.fifos.iter().enumerate() {
2270 if !f.queue.is_empty() || !f.pending.is_empty() {
2271 return Err(ChoreoFault::Unconsumed {
2272 buf: BufferId::new(i),
2273 pages_left: f.queue.len() + f.pending.len(),
2274 });
2275 }
2276 if f.reserved != 0 {
2277 return Err(ChoreoFault::OutstandingReservation {
2278 buf: BufferId::new(i),
2279 pages: f.reserved,
2280 });
2281 }
2282 }
2283 let t = self.t;
2284 let mut tensors = BTreeMap::new();
2285 for (td, store) in self.p.tensors.iter().zip(self.tensors.iter()) {
2286 if !every && !matches!(td.role, TensorRole::Output | TensorRole::State) {
2290 continue;
2291 }
2292 let (rows, cols) = (td.elems.rows, td.elems.cols);
2293 let is_int = precision_is_int(store.dtype);
2294 let mut ints = vec![0i64; rows * cols];
2295 let mut floats = vec![0f64; rows * cols];
2296 for (idx, tile) in store.tiles.iter().enumerate() {
2297 let Some(tile) = tile.as_ref().or(if self.dry {
2321 Some(&TileData::Empty)
2322 } else {
2323 None
2324 }) else {
2325 return Err(ChoreoFault::OutputTileNeverStored {
2326 tensor: td.id,
2327 tile: idx,
2328 });
2329 };
2330 let (rt, ct) = (idx / store.cols_t, idx % store.cols_t);
2331 for r in 0..t {
2332 for c in 0..t {
2333 let (er, ec) = (rt * t + r, ct * t + c);
2334 if er < rows && ec < cols {
2335 match tile {
2336 TileData::Int(v) => {
2337 ints[er * cols + ec] = v[r * t + c];
2338 floats[er * cols + ec] = v[r * t + c] as f64;
2339 }
2340 TileData::Float(v) => {
2341 floats[er * cols + ec] = v[r * t + c];
2342 ints[er * cols + ec] = v[r * t + c] as i64;
2343 }
2344 TileData::Empty => {}
2345 }
2346 }
2347 }
2348 }
2349 }
2350 tensors.insert(
2351 td.id,
2352 if is_int {
2353 Value::Int(ints)
2354 } else {
2355 Value::Float(floats)
2356 },
2357 );
2358 }
2359 let trace = Trace {
2360 high_water: self
2361 .fifos
2362 .iter()
2363 .enumerate()
2364 .map(|(i, f)| (BufferId::new(i), f.high_water))
2365 .collect(),
2366 steps: self.steps,
2367 tiles_read: std::mem::take(&mut self.tiles_read),
2368 held: std::mem::take(&mut self.held),
2369 nodes: std::mem::take(&mut self.nodes),
2370 stage_held: std::mem::take(&mut self.stage_held),
2371 };
2372 Ok((
2373 Outputs {
2374 tensors,
2375 steps: self.steps,
2376 },
2377 trace,
2378 ))
2379 }
2380}
2381
2382pub fn eval_choreography_with(
2383 p: &SpatialProgram,
2384 inputs: &Inputs,
2385 sched: Scheduler,
2386) -> Result<Outputs, ChoreoFault> {
2387 eval_choreography_traced_with(p, inputs, sched).map(|(o, _)| o)
2388}
2389
2390pub fn eval_choreography_traced_with(
2391 p: &SpatialProgram,
2392 inputs: &Inputs,
2393 sched: Scheduler,
2394) -> Result<(Outputs, Trace), ChoreoFault> {
2395 let mut e = Eval::new(p, inputs)?;
2396 e.run(sched)?;
2397 e.finish()
2398}
2399
2400pub fn eval_choreography_dry(p: &SpatialProgram) -> Result<Trace, ChoreoFault> {
2404 eval_choreography_dry_at(p, &p.bound_counts())
2405}
2406
2407pub fn eval_choreography_dry_at(
2409 p: &SpatialProgram,
2410 counts: &[usize],
2411) -> Result<Trace, ChoreoFault> {
2412 let mut e = Eval::new_dry(p, counts)?;
2413 e.run(Scheduler::RoundRobin)?;
2414 e.finish().map(|(_, t)| t)
2415}
2416
2417pub fn trip_counts(p: &SpatialProgram, inputs: &Inputs) -> Result<Vec<usize>, ChoreoFault> {
2421 p.trips
2422 .iter()
2423 .enumerate()
2424 .map(|(i, trip)| {
2425 let td = p.tensor(trip.tensor);
2426 let raw = match inputs.tensors.get(&td.id) {
2427 Some(Value::Int(v)) => v.get(trip.elem).map(|x| *x as f64),
2428 Some(Value::Float(v)) => v.get(trip.elem).copied(),
2429 None if td.init.is_float() => td.init.element_f64(trip.elem),
2430 None => td.init.element(trip.elem).map(|x| x as f64),
2431 }
2432 .ok_or(ChoreoFault::MissingInput(td.id))?;
2433 let held = match precision_int_bits(td.dtype) {
2434 Some(bits) => trunc_signed(raw as i64, bits) as f64,
2435 None => round_to(td.dtype, raw),
2436 };
2437 trip.count_of(held).ok_or(ChoreoFault::TripOutOfRange {
2438 trip: TripId::new(i),
2439 value: held,
2440 admitted: trip.admit,
2441 })
2442 })
2443 .collect()
2444}
2445
2446pub fn eval_choreography_every_tensor(
2449 p: &SpatialProgram,
2450 inputs: &Inputs,
2451) -> Result<Outputs, ChoreoFault> {
2452 let mut e = Eval::new(p, inputs)?;
2453 e.run(Scheduler::RoundRobin)?;
2454 e.finish_with(true).map(|(o, _)| o)
2455}
2456
2457pub fn eval_choreography(p: &SpatialProgram, inputs: &Inputs) -> Result<Outputs, ChoreoFault> {
2459 eval_choreography_with(p, inputs, Scheduler::RoundRobin)
2460}
2461
2462pub fn eval_choreography_traced(
2463 p: &SpatialProgram,
2464 inputs: &Inputs,
2465) -> Result<(Outputs, Trace), ChoreoFault> {
2466 eval_choreography_traced_with(p, inputs, Scheduler::RoundRobin)
2467}
2468
2469pub fn eval_choreography_confluent(
2471 p: &SpatialProgram,
2472 inputs: &Inputs,
2473) -> Result<Outputs, ChoreoFault> {
2474 let scheds = [
2475 Scheduler::RoundRobin,
2476 Scheduler::Reverse,
2477 Scheduler::Seeded(0x5EED),
2478 Scheduler::MovementFirst,
2479 Scheduler::ComputeFirst,
2480 ];
2481 let first = eval_choreography_with(p, inputs, scheds[0])?;
2482 for s in &scheds[1..] {
2483 let o = eval_choreography_with(p, inputs, *s)?;
2484 if o.tensors != first.tensors {
2485 let tensor = first
2486 .tensors
2487 .keys()
2488 .find(|k| o.tensors.get(k) != first.tensors.get(k))
2489 .copied()
2490 .unwrap_or(TensorId(0));
2491 return Err(ChoreoFault::NonDeterministic {
2492 scheduler_a: scheduler_name(scheds[0]),
2493 scheduler_b: scheduler_name(*s),
2494 tensor,
2495 });
2496 }
2497 }
2498 Ok(first)
2499}
2500
2501pub fn program_sites(p: &SpatialProgram) -> BTreeSet<Site> {
2503 p.schedule
2504 .as_ref()
2505 .map(|s| s.programs.iter().map(|q| q.site).collect())
2506 .unwrap_or_default()
2507}