1use std::collections::{BTreeMap, BTreeSet};
26
27use super::recognize_stages::Link;
28use super::SpatialReject;
29use crate::codegen_sva::fabric_target::Precision;
30use crate::spatial::ir::*;
31use crate::spatial::target::{precision_bytes, precision_is_int, SpatialTarget};
32
33fn wider_float(a: Precision, b: Precision) -> Option<Precision> {
36 if precision_is_int(a) || precision_is_int(b) {
37 return None;
38 }
39 match precision_bytes(a).cmp(&precision_bytes(b)) {
40 std::cmp::Ordering::Greater => Some(a),
41 std::cmp::Ordering::Less => Some(b),
42 std::cmp::Ordering::Equal => None,
43 }
44}
45
46#[derive(Clone, Debug, PartialEq)]
48pub enum ComposeReject {
49 NoSections,
51 TensorShapeConflict {
54 name: String,
55 first: (usize, usize, Precision),
56 second: (usize, usize, Precision),
57 },
58 SectionNotScheduled { section: usize },
60 Invalid(IrViolation),
62 Reject(Box<SpatialReject>),
64 PhaseOverflowsMemory { bytes: u32, capacity: usize },
66}
67
68impl From<IrViolation> for ComposeReject {
69 fn from(v: IrViolation) -> Self {
70 ComposeReject::Invalid(v)
71 }
72}
73
74impl From<SpatialReject> for ComposeReject {
75 fn from(v: SpatialReject) -> Self {
76 ComposeReject::Reject(Box::new(v))
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum SectionPlacement {
83 Stacked,
87 RoundRobin,
90}
91
92const RENDEZVOUS_SEMS: usize = 2;
94
95struct Shift {
97 tensor: Vec<TensorId>,
98 stage: usize,
99 buffer: usize,
100 edge: usize,
101 sem: usize,
102 item: usize,
103 group: usize,
104 trip: usize,
105}
106
107impl Shift {
108 fn t(&self, id: TensorId) -> TensorId {
109 self.tensor[id.idx()]
110 }
111 fn s(&self, id: StageId) -> StageId {
112 StageId::new(self.stage + id.idx())
113 }
114 fn b(&self, id: BufferId) -> BufferId {
115 BufferId::new(self.buffer + id.idx())
116 }
117 fn e(&self, id: EdgeId) -> EdgeId {
118 EdgeId::new(self.edge + id.idx())
119 }
120 fn m(&self, id: SemId) -> SemId {
121 SemId::new(self.sem + id.idx())
122 }
123 fn i(&self, id: WorkItemId) -> WorkItemId {
124 WorkItemId::new(self.item + id.idx())
125 }
126 fn g(&self, id: GroupId) -> GroupId {
127 GroupId::new(self.group + id.idx())
128 }
129 fn r(&self, id: TripId) -> TripId {
130 TripId::new(self.trip + id.idx())
131 }
132 fn count(&self, c: LoopCount) -> LoopCount {
133 match c {
134 LoopCount::Const(n) => LoopCount::Const(n),
135 LoopCount::Trip { trip, max } => LoopCount::Trip {
136 trip: self.r(trip),
137 max,
138 },
139 }
140 }
141 fn addr(&self, a: &TileAddr) -> TileAddr {
142 TileAddr {
143 base: a.base,
144 steps: a.steps.clone(),
145 indirect: a.indirect.map(|i| IndirectTile {
146 index: self.b(i.index),
147 ..i
148 }),
149 }
150 }
151 fn pattern(&self, a: &AccessPattern) -> AccessPattern {
152 AccessPattern {
153 origin: self.addr(&a.origin),
154 ..a.clone()
155 }
156 }
157 fn compute(&self, op: &ComputeOp) -> ComputeOp {
158 match op {
159 ComputeOp::MatmulTiles { interm, .. } => {
160 let mut o = op.clone();
161 if let ComputeOp::MatmulTiles { interm: i, .. } = &mut o {
162 *i = interm.map(|b| self.b(b));
163 }
164 o
165 }
166 ComputeOp::Bias { bias } => ComputeOp::Bias {
167 bias: self.b(*bias),
168 },
169 other => other.clone(),
170 }
171 }
172 fn handshake(&self, h: &Handshake) -> Handshake {
173 match h {
174 Handshake::Semaphore {
175 ready,
176 valid,
177 ready_count,
178 } => Handshake::Semaphore {
179 ready: self.m(*ready),
180 valid: self.m(*valid),
181 ready_count: *ready_count,
182 },
183 Handshake::Barrier { group } => Handshake::Barrier {
184 group: self.g(*group),
185 },
186 other => other.clone(),
187 }
188 }
189 fn kind(&self, k: &StageKind) -> StageKind {
190 match k {
191 StageKind::Load {
192 tensor,
193 pattern,
194 num_blocks,
195 multicast,
196 } => StageKind::Load {
197 tensor: self.t(*tensor),
198 pattern: self.pattern(pattern),
199 num_blocks: *num_blocks,
200 multicast: multicast.map(|g| self.g(g)),
201 },
202 StageKind::Store {
203 tensor,
204 pattern,
205 sub_blocks,
206 } => StageKind::Store {
207 tensor: self.t(*tensor),
208 pattern: self.pattern(pattern),
209 sub_blocks: *sub_blocks,
210 },
211 StageKind::Recv { group } => StageKind::Recv {
212 group: self.g(*group),
213 },
214 StageKind::AllReduce {
215 pool,
216 tensor,
217 combine,
218 } => StageKind::AllReduce {
219 pool: *pool,
220 tensor: self.t(*tensor),
221 combine: Box::new(self.compute(combine)),
222 },
223 StageKind::Compute(op) => StageKind::Compute(self.compute(op)),
224 StageKind::Scalar(b) => StageKind::Scalar(b.clone()),
225 }
226 }
227 fn op(&self, o: &ChoreoOp) -> ChoreoOp {
230 match o {
231 ChoreoOp::Reserve { buf, pages } => ChoreoOp::Reserve {
232 buf: self.b(*buf),
233 pages: *pages,
234 },
235 ChoreoOp::Push { buf, pages } => ChoreoOp::Push {
236 buf: self.b(*buf),
237 pages: *pages,
238 },
239 ChoreoOp::Wait { buf, pages } => ChoreoOp::Wait {
240 buf: self.b(*buf),
241 pages: *pages,
242 },
243 ChoreoOp::Pop { buf, pages } => ChoreoOp::Pop {
244 buf: self.b(*buf),
245 pages: *pages,
246 },
247 ChoreoOp::ReadTiles {
248 tensor,
249 at,
250 block_h,
251 block_w,
252 stride_w,
253 stride_h,
254 into,
255 } => ChoreoOp::ReadTiles {
256 tensor: self.t(*tensor),
257 at: self.addr(at),
258 block_h: *block_h,
259 block_w: *block_w,
260 stride_w: *stride_w,
261 stride_h: *stride_h,
262 into: self.b(*into),
263 },
264 ChoreoOp::WriteTiles {
265 from,
266 tensor,
267 at,
268 block_h,
269 block_w,
270 stride_w,
271 stride_h,
272 } => ChoreoOp::WriteTiles {
273 from: self.b(*from),
274 tensor: self.t(*tensor),
275 at: self.addr(at),
276 block_h: *block_h,
277 block_w: *block_w,
278 stride_w: *stride_w,
279 stride_h: *stride_h,
280 },
281 ChoreoOp::McastSend {
282 buf,
283 pages,
284 group,
285 hold_path,
286 } => ChoreoOp::McastSend {
287 buf: self.b(*buf),
288 pages: *pages,
289 group: self.g(*group),
290 hold_path: *hold_path,
291 },
292 ChoreoOp::McastSetValid { sem, group } => ChoreoOp::McastSetValid {
293 sem: self.m(*sem),
294 group: self.g(*group),
295 },
296 ChoreoOp::SemSet { sem, value } => ChoreoOp::SemSet {
297 sem: self.m(*sem),
298 value: *value,
299 },
300 ChoreoOp::SemInc { sem, at, by } => ChoreoOp::SemInc {
301 sem: self.m(*sem),
302 at: *at,
303 by: *by,
304 },
305 ChoreoOp::SemWait { sem, until } => ChoreoOp::SemWait {
306 sem: self.m(*sem),
307 until: *until,
308 },
309 ChoreoOp::Compute {
310 op,
311 ins,
312 acc_reload,
313 } => ChoreoOp::Compute {
314 op: self.compute(op),
315 ins: ins.iter().map(|b| self.b(*b)).collect(),
316 acc_reload: acc_reload.map(|b| self.b(b)),
317 },
318 ChoreoOp::PackAcc {
319 to,
320 tiles,
321 convert,
322 accumulate,
323 } => ChoreoOp::PackAcc {
324 to: self.b(*to),
325 tiles: *tiles,
326 convert: *convert,
327 accumulate: *accumulate,
328 },
329 ChoreoOp::Barrier { group } => ChoreoOp::Barrier {
330 group: self.g(*group),
331 },
332 ChoreoOp::Loop { var, count, body } => ChoreoOp::Loop {
333 var: *var,
334 count: self.count(*count),
335 body: body.iter().map(|t| self.tagged(t)).collect(),
336 },
337 other => other.clone(),
338 }
339 }
340 fn tagged(&self, t: &TaggedOp) -> TaggedOp {
341 TaggedOp {
342 stage: self.s(t.stage),
343 op: self.op(&t.op),
344 }
345 }
346}
347
348#[derive(Default)]
350struct Usage {
351 written_in: Vec<usize>,
352 read_in: Vec<usize>,
353 declared: Option<TensorRole>,
354}
355
356pub fn compose(
362 sections: Vec<SpatialProgram>,
363 links: &[Link],
364 state: &[String],
365 placement: SectionPlacement,
366 target: &SpatialTarget,
367) -> Result<SpatialProgram, ComposeReject> {
368 compose_phased(sections, links, state, placement, None, target)
369}
370
371pub fn compose_phased(
379 sections: Vec<SpatialProgram>,
380 links: &[Link],
381 state: &[String],
382 placement: SectionPlacement,
383 barrier_after: Option<&[bool]>,
384 target: &SpatialTarget,
385) -> Result<SpatialProgram, ComposeReject> {
386 let _ = links;
387 let closes = |k: usize| {
388 k + 1 < sections.len() && barrier_after.is_none_or(|b| b.get(k).copied().unwrap_or(true))
389 };
390 if sections.is_empty() {
391 return Err(ComposeReject::NoSections);
392 }
393 for (k, s) in sections.iter().enumerate() {
394 if s.schedule.is_none() || s.placement.is_none() || s.memory.is_none() {
395 return Err(ComposeReject::SectionNotScheduled { section: k });
396 }
397 }
398
399 let producer_decl = {
409 let mut chosen: BTreeMap<String, (TensorDecl, bool)> = BTreeMap::new();
410 for sec in sections.iter() {
411 let stored: Vec<TensorId> = sec
412 .stages
413 .iter()
414 .filter_map(|s| match &s.kind {
415 StageKind::Store { tensor, .. } => Some(*tensor),
416 _ => None,
417 })
418 .collect();
419 for td in &sec.tensors {
420 let writes = stored.contains(&td.id);
421 match chosen.get_mut(&td.name) {
422 None => {
423 chosen.insert(td.name.clone(), (td.clone(), writes));
424 }
425 Some((have, had_writer)) => {
426 if have.elems != td.elems {
427 return Err(ComposeReject::TensorShapeConflict {
428 name: td.name.clone(),
429 first: (have.elems.rows, have.elems.cols, have.dtype),
430 second: (td.elems.rows, td.elems.cols, td.dtype),
431 });
432 }
433 if writes && !*had_writer {
434 let keep_init = matches!(td.init, TensorInit::Zero)
444 && !matches!(have.init, TensorInit::Zero);
445 let prior = have.init.clone();
446 *have = td.clone();
447 if keep_init {
448 have.init = prior;
449 }
450 *had_writer = true;
451 } else if have.dtype != td.dtype && (writes == *had_writer) {
452 match (writes, wider_float(have.dtype, td.dtype)) {
458 (false, Some(wider)) => have.dtype = wider,
459 _ => {
460 return Err(ComposeReject::TensorShapeConflict {
461 name: td.name.clone(),
462 first: (have.elems.rows, have.elems.cols, have.dtype),
463 second: (td.elems.rows, td.elems.cols, td.dtype),
464 })
465 }
466 }
467 }
468 }
469 }
470 }
471 }
472 chosen
473 };
474 let mut tensors: Vec<TensorDecl> = Vec::new();
475 let mut by_name: BTreeMap<String, TensorId> = BTreeMap::new();
476 let mut usage: Vec<Usage> = Vec::new();
477 let mut shifts: Vec<Shift> = Vec::new();
478 let (
479 mut stage_at,
480 mut buffer_at,
481 mut edge_at,
482 mut sem_at,
483 mut item_at,
484 mut group_at,
485 mut trip_at,
486 ) = (0, 0, 0, 0, 0, 0, 0);
487 for (k, sec) in sections.iter().enumerate() {
488 let mut tmap = Vec::with_capacity(sec.tensors.len());
489 for td in &sec.tensors {
490 let id = match by_name.get(&td.name) {
491 Some(id) => *id,
492 None => {
493 let id = TensorId::new(tensors.len());
494 let mut decl = producer_decl
495 .get(&td.name)
496 .map(|(d, _)| d.clone())
497 .unwrap_or_else(|| td.clone());
498 decl.id = id;
499 tensors.push(decl);
500 usage.push(Usage::default());
501 by_name.insert(td.name.clone(), id);
502 id
503 }
504 };
505 let u = &mut usage[id.idx()];
507 if u.declared.is_none() && matches!(td.role, TensorRole::Input | TensorRole::Param) {
508 u.declared = Some(td.role);
509 }
510 tmap.push(id);
511 }
512 for s in &sec.stages {
513 match &s.kind {
514 StageKind::Load { tensor, .. } => {
516 let id = tmap[tensor.idx()];
517 let u = &mut usage[id.idx()];
518 if u.read_in.last() != Some(&k) {
519 u.read_in.push(k);
520 }
521 }
522 StageKind::Store { tensor, .. } => {
523 let id = tmap[tensor.idx()];
524 let u = &mut usage[id.idx()];
525 if u.written_in.last() != Some(&k) {
526 u.written_in.push(k);
527 }
528 }
529 _ => {}
530 }
531 }
532 shifts.push(Shift {
533 tensor: tmap,
534 stage: stage_at,
535 buffer: buffer_at,
536 edge: edge_at,
537 sem: sem_at,
538 item: item_at,
539 group: group_at,
540 trip: trip_at,
541 });
542 stage_at += sec.stages.len();
543 buffer_at += sec.buffers.len();
544 edge_at += sec.edges.len();
545 sem_at += sec.semaphores.len() + if closes(k) { RENDEZVOUS_SEMS } else { 0 };
546 item_at += sec.work.items.len();
547 group_at += sec.placement.as_ref().map(|p| p.groups.len()).unwrap_or(0);
548 trip_at += sec.trips.len();
549 }
550
551 let carried: BTreeSet<&str> = state.iter().map(String::as_str).collect();
559 for (i, t) in tensors.iter_mut().enumerate() {
560 let u = &usage[i];
561 let written = !u.written_in.is_empty();
562 let read = !u.read_in.is_empty();
563 if carried.contains(t.name.as_str()) {
564 t.role = TensorRole::State;
565 } else if written && read {
566 t.role = TensorRole::Activation;
567 } else if written {
568 t.role = TensorRole::Output;
569 } else {
570 t.role = u.declared.unwrap_or(TensorRole::Input);
571 }
572 }
573
574 let mut stages: Vec<Stage> = Vec::new();
576 let mut buffers: Vec<Buffer> = Vec::new();
577 let mut edges: Vec<Edge> = Vec::new();
578 let mut semaphores: Vec<Semaphore> = Vec::new();
579 let mut items: Vec<WorkItem> = Vec::new();
580 let mut groups: Vec<Group> = Vec::new();
581 let mut trips: Vec<Trip> = Vec::new();
582 let mut site_of: Vec<Site> = Vec::new();
583 let mut engine_of: Vec<EngineSlot> = Vec::new();
584 let mut buffer_map: Vec<BufferAt> = Vec::new();
585 let mut items_of_site: BTreeMap<Site, Vec<WorkItemId>> = BTreeMap::new();
586 let mut requires = Requirements::default();
587 let mut acc_tiles = 0usize;
588 let mut exact = true;
589 let mut approx_reasons: Vec<String> = Vec::new();
590 let mesh = target.sites;
591 let mesh_sites: Vec<Site> = (0..mesh.y)
592 .flat_map(|y| (0..mesh.x).map(move |x| Site::new(x, y)))
593 .collect();
594
595 let order: Vec<(Site, Engine)> = {
598 let mut seen: BTreeSet<(Site, Engine)> = BTreeSet::new();
599 let mut v: Vec<(Site, Engine)> = Vec::new();
600 for (k, sec) in sections.iter().enumerate() {
601 for prog in &sec.schedule.as_ref().expect("checked").programs {
602 let site = match placement {
603 SectionPlacement::Stacked => prog.site,
604 SectionPlacement::RoundRobin => *mesh_sites
605 .get(k % mesh_sites.len().max(1))
606 .unwrap_or(&prog.site),
607 };
608 if seen.insert((site, prog.engine)) {
609 v.push((site, prog.engine));
610 }
611 }
612 }
613 v.sort_by_key(|(s, e)| (s.y, s.x, *e));
614 v
615 };
616 let participants = order.len();
617 let root = order.first().copied();
618 let rendezvous_sites: Vec<Site> = {
619 let mut v: Vec<Site> = order.iter().map(|(s, _)| *s).collect();
620 v.sort_by_key(|s| (s.y, s.x, s.chip));
621 v.dedup();
622 v
623 };
624 let rendezvous: Vec<Option<Rendezvous>> = (0..sections.len())
625 .map(|k| {
626 closes(k).then(|| {
627 let base = shifts[k].sem + sections[k].semaphores.len();
628 Rendezvous {
629 gather: SemId::new(base),
630 release: SemId::new(base + 1),
631 }
632 })
633 })
634 .collect();
635
636 for (k, sec) in sections.iter().enumerate() {
637 let sh = &shifts[k];
638 let move_site = |s: Site| -> Site {
641 match placement {
642 SectionPlacement::Stacked => s,
643 SectionPlacement::RoundRobin => {
644 *mesh_sites.get(k % mesh_sites.len().max(1)).unwrap_or(&s)
645 }
646 }
647 };
648 let pl = sec.placement.as_ref().expect("checked above");
649 let mm = sec.memory.as_ref().expect("checked above");
650 for s in &sec.stages {
651 stages.push(Stage {
652 id: sh.s(s.id),
653 name: format!("s{k}.{}", s.name),
654 kind: sh.kind(&s.kind),
655 role: s.role,
656 ins: s.ins.iter().map(|b| sh.b(*b)).collect(),
657 outs: s.outs.iter().map(|b| sh.b(*b)).collect(),
658 site: s.site.map(move_site),
659 item: s.item.map(|i| sh.i(i)),
660 origin: s.origin.map(|o| sh.s(o)),
661 });
662 }
663 for b in &sec.buffers {
664 buffers.push(Buffer {
665 id: sh.b(b.id),
666 name: format!("s{k}.{}", b.name),
667 kind: b.kind,
668 dtype: b.dtype,
669 space: b.space,
670 site: b.site.map(move_site),
671 origin: b.origin.map(|o| sh.b(o)),
672 alias_of: b.alias_of.map(|a| sh.b(a)),
673 });
674 let at = mm.buffer_at[b.id.idx()];
675 buffer_map.push(BufferAt {
676 site: move_site(at.site),
677 ..at
678 });
679 }
680 for e in &sec.edges {
681 edges.push(Edge {
682 id: sh.e(e.id),
683 from: sh.s(e.from),
684 buffer: sh.b(e.buffer),
685 to: e.to.iter().map(|t| sh.s(*t)).collect(),
686 handshake: sh.handshake(&e.handshake),
687 });
688 }
689 for s in &sec.semaphores {
690 semaphores.push(Semaphore {
691 id: sh.m(s.id),
692 init: s.init,
693 role: s.role,
694 operand: s.operand,
695 sites: s.sites.iter().map(|x| move_site(*x)).collect(),
696 });
697 }
698 if let (Some(r), Some((root_site, _))) = (rendezvous[k], root) {
699 let n = participants;
700 semaphores.push(Semaphore {
701 id: r.gather,
702 init: 0,
703 role: SemRole::Barrier {
704 n,
705 phase: BarrierPhase::Gather,
706 },
707 operand: None,
708 sites: vec![root_site],
709 });
710 semaphores.push(Semaphore {
711 id: r.release,
712 init: 0,
713 role: SemRole::Barrier {
714 n,
715 phase: BarrierPhase::Release,
716 },
717 operand: None,
718 sites: rendezvous_sites.clone(),
719 });
720 }
721 for it in &sec.work.items {
722 items.push(WorkItem {
723 id: sh.i(it.id),
724 coord: it.coord,
725 out: it.out,
726 });
727 }
728 trips.extend(sec.trips.iter().map(|t| Trip {
730 tensor: sh.t(t.tensor),
731 ..t.clone()
732 }));
733 for g in &pl.groups {
734 groups.push(Group {
735 id: sh.g(g.id),
736 rect: g.rect,
737 sender: move_site(g.sender),
738 direction: g.direction,
739 operand: g.operand,
740 noc: g.noc,
741 });
742 }
743 for s in &sec.stages {
744 site_of.push(move_site(pl.site_of[s.id.idx()]));
745 let slot = &pl.engine_of[s.id.idx()];
746 engine_of.push(EngineSlot {
747 site: move_site(slot.site),
748 engine: slot.engine,
749 noc: slot.noc,
750 });
751 }
752 for (site, ids) in &pl.items_of_site {
753 items_of_site
754 .entry(move_site(*site))
755 .or_default()
756 .extend(ids.iter().map(|i| sh.i(*i)));
757 }
758 requires
759 .handshakes
760 .extend(sec.requires.handshakes.iter().copied());
761 requires
762 .interconnect
763 .extend(sec.requires.interconnect.iter().copied());
764 acc_tiles = acc_tiles.max(sec.acc_tiles_used);
765 match &sec.claim {
766 NumericsClaim::Exact => {}
767 NumericsClaim::Approx { reason } => {
768 exact = false;
769 if !approx_reasons.contains(reason) {
770 approx_reasons.push(reason.clone());
771 }
772 }
773 }
774 }
775
776 let participant: BTreeMap<(Site, Engine), usize> =
782 order.iter().enumerate().map(|(i, p)| (*p, i)).collect();
783 let mut ops: Vec<Vec<TaggedOp>> = vec![Vec::new(); participants];
784 let mut stage_ids: Vec<Vec<StageId>> = vec![Vec::new(); participants];
785 for (k, sec) in sections.iter().enumerate() {
786 let sh = &shifts[k];
787 let own = &sec.schedule.as_ref().expect("checked").programs;
788 let runs: Vec<(usize, &EngineProgram)> = match placement {
791 SectionPlacement::Stacked => own
792 .iter()
793 .filter_map(|q| participant.get(&(q.site, q.engine)).map(|i| (*i, q)))
794 .collect(),
795 SectionPlacement::RoundRobin => order
796 .iter()
797 .enumerate()
798 .filter_map(|(i, (_, engine))| {
799 own.iter().find(|q| q.engine == *engine).map(|q| (i, q))
800 })
801 .collect(),
802 };
803 let mut anchor: BTreeMap<usize, StageId> = BTreeMap::new();
806 for (i, prog) in runs {
807 for s in &prog.stages {
808 let id = sh.s(*s);
809 stage_ids[i].push(id);
810 anchor.entry(i).or_insert(id);
811 }
812 ops[i].extend(prog.ops.iter().map(|t| sh.tagged(t)));
813 }
814 if let (Some(r), Some((root_site, _))) = (rendezvous[k], root) {
815 let fallback = sec
816 .stages
817 .first()
818 .map(|st| sh.s(st.id))
819 .unwrap_or(StageId::new(0));
820 for (i, (site, engine)) in order.iter().enumerate() {
821 let tag = anchor.get(&i).copied().unwrap_or(fallback);
822 let ops = &mut ops[i];
823 ops.push(TaggedOp {
826 stage: tag,
827 op: ChoreoOp::SemInc {
828 sem: r.gather,
829 at: root_site,
830 by: 1,
831 },
832 });
833 if root == Some((*site, *engine)) {
834 ops.push(TaggedOp {
835 stage: tag,
836 op: ChoreoOp::SemWait {
837 sem: r.gather,
838 until: SemCond::AtLeast(participants),
839 },
840 });
841 for s in &rendezvous_sites {
842 ops.push(TaggedOp {
843 stage: tag,
844 op: ChoreoOp::SemInc {
845 sem: r.release,
846 at: *s,
847 by: 1,
848 },
849 });
850 }
851 }
852 ops.push(TaggedOp {
853 stage: tag,
854 op: ChoreoOp::SemWait {
855 sem: r.release,
856 until: SemCond::AtLeast(1),
857 },
858 });
859 }
860 }
861 }
862 let mut programs: Vec<EngineProgram> = Vec::new();
863 for (((site, engine), ops), mut stage_ids) in order.iter().zip(ops).zip(stage_ids) {
864 if ops.is_empty() {
865 continue;
866 }
867 stage_ids.sort();
868 stage_ids.dedup();
869 programs.push(EngineProgram {
870 site: *site,
871 engine: *engine,
872 stages: stage_ids,
873 ops,
874 });
875 }
876
877 let mut spans: Vec<SectionSpan> = Vec::with_capacity(sections.len());
879 {
880 let (mut st, mut bf, mut ed, mut sm, mut it) = (0usize, 0usize, 0usize, 0usize, 0usize);
881 for (k, sec) in sections.iter().enumerate() {
882 let sem_end = shifts[k].sem
883 + sec.semaphores.len()
884 + if rendezvous[k].is_some() {
885 RENDEZVOUS_SEMS
886 } else {
887 0
888 };
889 let out = sec.output_tensor().map(|t| shifts[k].t(t.id));
892 spans.push(SectionSpan {
893 name: sec.name.clone(),
894 stages: (st, st + sec.stages.len()),
895 buffers: (bf, bf + sec.buffers.len()),
896 edges: (ed, ed + sec.edges.len()),
897 semaphores: (sm, sem_end),
898 items: (it, it + sec.work.items.len()),
899 plan: sec.work.plan,
900 k_tiles: sec.work.k_tiles,
901 output: out,
902 barrier_after: rendezvous[k],
903 });
904 st += sec.stages.len();
905 bf += sec.buffers.len();
906 ed += sec.edges.len();
907 sm = sem_end;
908 it += sec.work.items.len();
909 }
910 }
911
912 let last = sections.last().expect("non-empty");
913 let out_name = last
914 .tensors
915 .iter()
916 .find(|t| t.role == TensorRole::Output)
917 .map(|t| t.name.clone())
918 .unwrap_or_else(|| "result".into());
919 let claim = if exact {
920 NumericsClaim::Exact
921 } else {
922 NumericsClaim::Approx {
923 reason: approx_reasons.join("; "),
924 }
925 };
926 let mut p = SpatialProgram {
927 name: if sections.len() == 1 {
932 sections[0].name.clone()
933 } else {
934 format!("{out_name}_{}_sections", sections.len())
935 },
936 tile: target.tile,
937 tensors,
938 stages,
939 buffers,
940 edges,
941 semaphores,
942 sections: spans,
943 trips,
944 links: Vec::new(),
945 work: WorkList {
946 items,
947 k_tiles: last.work.k_tiles,
948 plan: last.work.plan,
949 k_trip: last.work.k_trip.map(|t| shifts[sections.len() - 1].r(t)),
950 },
951 numerics: if sections.iter().all(|s| s.numerics == NumericsPolicy::Exact) {
952 NumericsPolicy::Exact
953 } else {
954 NumericsPolicy::AllowSpillRounding
955 },
956 claim,
957 acc_tiles_used: acc_tiles,
958 mem_banks: target.site.mem_banks,
959 requires,
960 placement: Some(Placement {
961 mesh,
962 site_of,
963 engine_of,
964 groups,
965 items_of_site: items_of_site.into_iter().collect(),
966 }),
967 memory: Some(MemoryMap {
968 buffer_at: buffer_map,
969 }),
970 schedule: Some(Schedule { programs }),
971 certificate: None,
972 };
973 let high = separate_phase_memory(&mut p);
974 if high as usize > target.max_local_mem_bytes() {
975 return Err(ComposeReject::PhaseOverflowsMemory {
976 bytes: high,
977 capacity: target.max_local_mem_bytes(),
978 });
979 }
980 crate::spatial::residency::place_activations(&mut p, target);
984 crate::spatial::residency::plan_deployment(&mut [("program", 1, &mut p, None)], target);
985 p.validate()?;
986 Ok(p)
987}
988
989pub fn separate_phase_memory(p: &mut SpatialProgram) -> u32 {
995 let Some(mm) = p.memory.as_mut() else {
996 return 0;
997 };
998 let mut end: BTreeMap<(Site, MemSpace), u32> = BTreeMap::new();
999 let mut high = 0u32;
1000 for sp in &p.sections {
1001 let mut mine: BTreeMap<(Site, MemSpace), u32> = BTreeMap::new();
1002 for b in sp.buffers.0..sp.buffers.1 {
1003 let buf = &p.buffers[b];
1004 if buf.space == MemSpace::Acc {
1005 continue;
1006 }
1007 let at = &mut mm.buffer_at[b];
1008 at.offset_bytes += end.get(&(at.site, at.space)).copied().unwrap_or(0);
1009 if buf.alias_of.is_none() {
1010 let e = mine.entry((at.site, at.space)).or_default();
1011 *e = (*e).max(at.offset_bytes + at.bytes);
1012 high = high.max(at.offset_bytes + at.bytes);
1013 }
1014 }
1015 for (key, e) in mine {
1016 let x = end.entry(key).or_default();
1017 *x = (*x).max(e);
1018 }
1019 if sp.barrier_after.is_some() {
1020 end.clear();
1021 }
1022 }
1023 high
1024}