1use super::{SpatialReject, LV_KBLOCK, LV_SUB_H, LV_SUB_W};
12
13fn pages_per_item(p: &SpatialProgram, buf: BufferId, depth: usize) -> usize {
19 if depth > p.stages.len() {
20 return 1;
21 }
22 let Some(e) = p.edges.iter().find(|e| e.buffer == buf) else {
23 return 1;
24 };
25 let producer = p.stage(e.from);
26 match &producer.kind {
27 StageKind::Compute(ComputeOp::MatmulTiles { sub_blocks, .. }) => {
28 sub_blocks.0 * sub_blocks.1
29 }
30 StageKind::Compute(_) | StageKind::Scalar(_) | StageKind::AllReduce { .. } => producer
31 .ins
32 .first()
33 .map(|b| pages_per_item(p, *b, depth + 1))
34 .unwrap_or(1),
35 StageKind::Load {
36 pattern,
37 num_blocks,
38 ..
39 } => {
40 if pattern
41 .origin
42 .steps
43 .iter()
44 .any(|(v, _)| *v == LV_SUB_H || *v == LV_SUB_W)
45 {
46 p.work
47 .plan
48 .map(|pl| {
49 (pl.per_site_m / pl.block.osh.max(1))
50 * (pl.per_site_n / pl.block.osw.max(1))
51 })
52 .unwrap_or(1)
53 } else {
54 *num_blocks
55 }
56 }
57 StageKind::Recv { .. } => p.work.plan.map(|pl| pl.inner.num_blocks).unwrap_or(1),
58 StageKind::Store { .. } => 1,
59 }
60}
61use crate::spatial::cost::is_streaming;
62use crate::spatial::ir::*;
63use crate::spatial::target::{SpatialTarget, WriteOrdering};
64use std::collections::BTreeMap;
65
66fn stage_group_semaphores(
67 p: &SpatialProgram,
68 stage: StageId,
69) -> Option<(GroupId, SemId, SemId, usize)> {
70 let s = p.stage(stage);
71 let g = p.stage_group(s)?;
72 for e in &p.edges {
73 if let Handshake::Semaphore {
74 ready,
75 valid,
76 ready_count,
77 } = &e.handshake
78 {
79 let sender_is_me = e.from == stage;
80 let receiver_is_me = e.to.contains(&stage);
81 if (sender_is_me || receiver_is_me) && p.stage_group(p.stage(e.from)) == Some(g) {
82 return Some((g, *ready, *valid, *ready_count));
83 }
84 }
85 }
86 None
87}
88
89fn reader_block(p: &SpatialProgram, target: &SpatialTarget, s: &Stage, out: &mut Vec<TaggedOp>) {
91 let buf = s.outs[0];
92 let tag = |op: ChoreoOp| TaggedOp { stage: s.id, op };
93 match &s.kind {
94 StageKind::Load {
95 tensor,
96 pattern,
97 multicast,
98 ..
99 } => {
100 let index = pattern.origin.indirect.map(|i| i.index);
104 if let Some(ix) = index {
105 out.push(tag(ChoreoOp::Wait { buf: ix, pages: 1 }));
106 }
107 out.push(tag(ChoreoOp::Reserve { buf, pages: 1 }));
108 out.push(tag(ChoreoOp::ReadTiles {
109 tensor: *tensor,
110 at: pattern.origin.clone(),
111 block_h: pattern.block_h,
112 block_w: pattern.block_w,
113 stride_w: pattern.stride_w,
114 stride_h: pattern.stride_h,
115 into: buf,
116 }));
117 out.push(tag(ChoreoOp::ReadBarrier));
118 if let Some(ix) = index {
119 out.push(tag(ChoreoOp::Pop { buf: ix, pages: 1 }));
120 }
121 if let Some(g) = multicast {
122 let (_, ready, valid, n) = stage_group_semaphores(p, s.id)
123 .expect("a multicasting load has a semaphore edge");
124 out.push(tag(ChoreoOp::SemWait {
125 sem: ready,
126 until: SemCond::Equals(n),
127 }));
128 out.push(tag(ChoreoOp::SemSet {
129 sem: ready,
130 value: SemValue::Count(0),
131 }));
132 let hold_path = target.write_ordering != WriteOrdering::NeedsFlush;
136 out.push(tag(ChoreoOp::McastSend {
137 buf,
138 pages: 1,
139 group: *g,
140 hold_path,
141 }));
142 if target.write_ordering == WriteOrdering::NeedsFlush {
143 out.push(tag(ChoreoOp::Flush {
144 cond: ArchCond::Blackhole,
145 }));
146 }
147 out.push(tag(ChoreoOp::McastSetValid {
148 sem: valid,
149 group: *g,
150 }));
151 }
152 out.push(tag(ChoreoOp::Push { buf, pages: 1 }));
153 }
154 StageKind::Recv { group } => {
155 let (_, ready, valid, _) =
156 stage_group_semaphores(p, s.id).expect("a receiver has a semaphore edge");
157 let sender = p.placement.as_ref().unwrap().groups[group.idx()].sender;
158 out.push(tag(ChoreoOp::Reserve { buf, pages: 1 }));
159 out.push(tag(ChoreoOp::SemSet {
160 sem: valid,
161 value: SemValue::Invalid,
162 }));
163 out.push(tag(ChoreoOp::SemInc {
164 sem: ready,
165 at: sender,
166 by: 1,
167 }));
168 out.push(tag(ChoreoOp::SemWait {
169 sem: valid,
170 until: SemCond::Equals(SemValue::Valid.as_u32() as usize),
171 }));
172 out.push(tag(ChoreoOp::Push { buf, pages: 1 }));
173 }
174 _ => {}
175 }
176}
177
178fn sub_block_body_regs(s: &Stage, op: &ComputeOp, ins: &[BufferId], tiles: usize) -> Vec<ChoreoOp> {
184 let _ = (s, tiles);
185 vec![ChoreoOp::Compute {
186 op: op.clone(),
187 ins: ins.to_vec(),
188 acc_reload: None,
189 }]
190}
191
192fn sub_block_body(
193 s: &Stage,
194 op: &ComputeOp,
195 ins: &[BufferId],
196 reload: Option<BufferId>,
197 spill_to: BufferId,
198 tiles: usize,
199 convert: crate::codegen_sva::fabric_target::Precision,
200) -> Vec<ChoreoOp> {
201 sub_block_body_acc(s, op, ins, reload, spill_to, tiles, convert, false)
202}
203
204#[allow(clippy::too_many_arguments)]
206fn sub_block_body_acc(
207 s: &Stage,
208 op: &ComputeOp,
209 ins: &[BufferId],
210 reload: Option<BufferId>,
211 spill_to: BufferId,
212 tiles: usize,
213 convert: crate::codegen_sva::fabric_target::Precision,
214 accumulate: bool,
215) -> Vec<ChoreoOp> {
216 let _ = s;
217 let mut body = vec![ChoreoOp::AcquireAcc { tiles }];
218 if let Some(interm) = reload {
219 body.push(ChoreoOp::Wait {
220 buf: interm,
221 pages: 1,
222 });
223 }
224 body.push(ChoreoOp::Compute {
225 op: op.clone(),
226 ins: ins.to_vec(),
227 acc_reload: reload,
228 });
229 if let Some(interm) = reload {
230 body.push(ChoreoOp::Pop {
231 buf: interm,
232 pages: 1,
233 });
234 }
235 body.push(ChoreoOp::Reserve {
236 buf: spill_to,
237 pages: 1,
238 });
239 body.push(ChoreoOp::PackAcc {
240 to: spill_to,
241 tiles,
242 convert,
243 accumulate,
244 });
245 body.push(ChoreoOp::Push {
246 buf: spill_to,
247 pages: 1,
248 });
249 body.push(ChoreoOp::ReleaseAcc);
250 body
251}
252
253fn tagged(stage: StageId, ops: Vec<ChoreoOp>) -> Vec<TaggedOp> {
255 ops.into_iter().map(|op| TaggedOp { stage, op }).collect()
256}
257
258fn sub_loops(sub_blocks: (usize, usize), body: Vec<TaggedOp>) -> Vec<TaggedOp> {
260 let stage = body.first().map(|t| t.stage).unwrap_or(StageId::new(0));
261 let inner = if sub_blocks.1 > 1 {
262 vec![TaggedOp {
263 stage,
264 op: ChoreoOp::Loop {
265 var: LV_SUB_W,
266 count: LoopCount::Const(sub_blocks.1),
267 body,
268 },
269 }]
270 } else {
271 body
272 };
273 if sub_blocks.0 > 1 {
274 vec![TaggedOp {
275 stage,
276 op: ChoreoOp::Loop {
277 var: LV_SUB_H,
278 count: LoopCount::Const(sub_blocks.0),
279 body: inner,
280 },
281 }]
282 } else {
283 inner
284 }
285}
286
287fn touches(
289 ops: &[TaggedOp],
290) -> (
291 std::collections::BTreeSet<usize>,
292 std::collections::BTreeSet<usize>,
293) {
294 let (mut produces, mut consumes) = (
295 std::collections::BTreeSet::new(),
296 std::collections::BTreeSet::new(),
297 );
298 for t in ops {
299 match &t.op {
300 ChoreoOp::Push { buf, .. } | ChoreoOp::Reserve { buf, .. } => {
301 produces.insert(buf.idx());
302 }
303 ChoreoOp::Wait { buf, .. } | ChoreoOp::Pop { buf, .. } => {
304 consumes.insert(buf.idx());
305 }
306 ChoreoOp::Loop { body, .. } => {
307 let (pr, co) = touches(body);
308 produces.extend(pr);
309 consumes.extend(co);
310 }
311 _ => {}
312 }
313 }
314 (produces, consumes)
315}
316
317fn fuse_adjacent_loops(ops: Vec<TaggedOp>) -> Vec<TaggedOp> {
329 let mut out: Vec<TaggedOp> = Vec::with_capacity(ops.len());
330 for t in ops {
331 let merged = match (out.last_mut().map(|l| &mut l.op), &t.op) {
332 (
333 Some(ChoreoOp::Loop {
334 var: pv,
335 count: pc,
336 body: pb,
337 }),
338 ChoreoOp::Loop { var, count, body },
339 ) if pv == var && pc == count => {
340 let (produces, _) = touches(pb);
341 let (_, consumes) = touches(body);
342 if produces.is_disjoint(&consumes) {
343 pb.extend(body.iter().cloned());
344 true
345 } else {
346 false
347 }
348 }
349 _ => false,
350 };
351 if !merged {
352 out.push(t);
353 }
354 }
355 for t in out.iter_mut() {
357 if let ChoreoOp::Loop { body, .. } = &mut t.op {
358 *body = fuse_adjacent_loops(std::mem::take(body));
359 }
360 }
361 out
362}
363
364fn stage_ranks(p: &SpatialProgram) -> Vec<usize> {
373 let n = p.stages.len();
374 let mut producer_of: std::collections::BTreeMap<usize, Vec<usize>> = Default::default();
375 for s in &p.stages {
376 for b in &s.outs {
377 producer_of.entry(b.idx()).or_default().push(s.id.idx());
378 }
379 }
380 let carries = p.carries();
384 let levels = if carries.is_empty() {
385 vec![FoldLevel::Prologue; n]
386 } else {
387 p.fold_levels()
388 };
389 let mut indeg = vec![0usize; n];
390 let mut succ: Vec<Vec<usize>> = vec![Vec::new(); n];
391 let mut arc = |from: usize, to: usize, succ: &mut Vec<Vec<usize>>| {
392 succ[from].push(to);
393 indeg[to] += 1;
394 };
395 for s in &p.stages {
396 for b in &s.ins {
397 let update = carries.get(b).map(|c| c.idx());
398 for &pr in producer_of
399 .get(&b.idx())
400 .map(|v| v.as_slice())
401 .unwrap_or(&[])
402 {
403 if pr == s.id.idx() {
404 continue;
405 }
406 if Some(pr) == update && levels[s.id.idx()] == FoldLevel::Body {
407 arc(s.id.idx(), pr, &mut succ);
408 } else {
409 arc(pr, s.id.idx(), &mut succ);
410 }
411 }
412 }
413 }
414 let mut ready: std::collections::BTreeSet<usize> = (0..n).filter(|i| indeg[*i] == 0).collect();
415 let mut rank = vec![usize::MAX; n];
416 let mut next = 0usize;
417 while let Some(&i) = ready.iter().next() {
418 ready.remove(&i);
419 rank[i] = next;
420 next += 1;
421 for &t in &succ[i] {
422 indeg[t] -= 1;
423 if indeg[t] == 0 {
424 ready.insert(t);
425 }
426 }
427 }
428 for (i, r) in rank.iter_mut().enumerate() {
430 if *r == usize::MAX {
431 *r = next + i;
432 }
433 }
434 rank
435}
436
437fn chain_ops(p: &SpatialProgram, chain: &[&Stage]) -> Vec<TaggedOp> {
446 let mut last_use: std::collections::BTreeMap<BufferId, usize> = Default::default();
447 for (i, s) in chain.iter().enumerate() {
448 for b in operands(s) {
449 last_use.insert(b, i);
450 }
451 }
452 let mut body: Vec<TaggedOp> = Vec::new();
453 for (i, s) in chain.iter().enumerate() {
454 let op = match &s.kind {
455 StageKind::Compute(op) => op.clone(),
456 StageKind::Scalar(b) => ComputeOp::Scalar(b.clone()),
457 StageKind::AllReduce { combine, .. } => (**combine).clone(),
458 _ => continue,
459 };
460 let out = s.outs[0];
461 let out_dtype = p.buffer(out).dtype;
462 let tiles = p.buffer(out).kind.page_tiles();
463 let reads = operands(s);
464 let mut seq: Vec<ChoreoOp> = reads
465 .iter()
466 .map(|b| ChoreoOp::Wait { buf: *b, pages: 1 })
467 .collect();
468 seq.extend([
469 ChoreoOp::AcquireAcc { tiles },
470 ChoreoOp::Compute {
471 op,
472 ins: s.ins.clone(),
473 acc_reload: None,
474 },
475 ChoreoOp::Reserve { buf: out, pages: 1 },
476 ChoreoOp::PackAcc {
477 to: out,
478 tiles,
479 convert: out_dtype,
480 accumulate: false,
481 },
482 ChoreoOp::Push { buf: out, pages: 1 },
483 ChoreoOp::ReleaseAcc,
484 ]);
485 seq.extend(
486 reads
487 .iter()
488 .filter(|b| last_use.get(b) == Some(&i))
489 .map(|b| ChoreoOp::Pop { buf: *b, pages: 1 }),
490 );
491 body.extend(tagged(s.id, seq));
492 }
493 body
494}
495
496fn operands(s: &Stage) -> Vec<BufferId> {
499 let mut out: Vec<BufferId> = Vec::new();
500 let mut push = |b: BufferId| {
501 if !out.contains(&b) {
502 out.push(b);
503 }
504 };
505 for b in &s.ins {
506 push(*b);
507 }
508 out
509}
510
511fn stage_ops(p: &SpatialProgram, s: &Stage, pops: &[BufferId]) -> Vec<TaggedOp> {
513 let op = match &s.kind {
514 StageKind::Compute(op) => op.clone(),
515 StageKind::Scalar(b) => ComputeOp::Scalar(b.clone()),
516 StageKind::AllReduce { combine, .. } => (**combine).clone(),
517 _ => return Vec::new(),
518 };
519 let out = s.outs[0];
520 let tiles = p.buffer(out).kind.page_tiles();
521 let mut seq: Vec<ChoreoOp> = operands(s)
522 .into_iter()
523 .map(|b| ChoreoOp::Wait { buf: b, pages: 1 })
524 .collect();
525 seq.extend([
526 ChoreoOp::AcquireAcc { tiles },
527 ChoreoOp::Compute {
528 op,
529 ins: s.ins.clone(),
530 acc_reload: None,
531 },
532 ChoreoOp::Reserve { buf: out, pages: 1 },
533 ChoreoOp::PackAcc {
534 to: out,
535 tiles,
536 convert: p.buffer(out).dtype,
537 accumulate: false,
538 },
539 ChoreoOp::Push { buf: out, pages: 1 },
540 ChoreoOp::ReleaseAcc,
541 ]);
542 seq.extend(pops.iter().map(|b| ChoreoOp::Pop { buf: *b, pages: 1 }));
543 tagged(s.id, seq)
544}
545
546fn k_count(p: &SpatialProgram, nb: usize) -> LoopCount {
555 match p.work.k_trip {
556 Some(trip) => LoopCount::Trip { trip, max: nb },
557 None => LoopCount::Const(nb),
558 }
559}
560
561fn fold_ops(
562 p: &SpatialProgram,
563 chain: &[&Stage],
564 levels: &[FoldLevel],
565 nb: usize,
566) -> Vec<TaggedOp> {
567 let carries = p.carries();
568 let at = |l: FoldLevel| {
569 chain
570 .iter()
571 .copied()
572 .filter(|s| levels[s.id.idx()] == l)
573 .collect::<Vec<&Stage>>()
574 };
575 let (pro, body, epi) = (
576 at(FoldLevel::Prologue),
577 at(FoldLevel::Body),
578 at(FoldLevel::Epilogue),
579 );
580 let last_reader = |stages: &[&Stage], b: BufferId| {
581 stages
582 .iter()
583 .rev()
584 .find(|s| s.ins.contains(&b))
585 .map(|s| s.id)
586 };
587 let producer_level = |b: BufferId| {
588 p.stages
589 .iter()
590 .filter(|s| s.outs.contains(&b))
591 .map(|s| levels[s.id.idx()])
592 .max()
593 .unwrap_or(FoldLevel::Prologue)
594 };
595 let mut pops: BTreeMap<StageId, Vec<BufferId>> = BTreeMap::new();
596 let mut after_blocks: Vec<BufferId> = Vec::new();
597 let mut read: Vec<BufferId> = Vec::new();
598 for s in chain {
599 for b in operands(s) {
600 if !read.contains(&b) {
601 read.push(b);
602 }
603 }
604 }
605 for b in read {
606 let final_pop = |pops: &mut BTreeMap<StageId, Vec<BufferId>>, after: &mut Vec<BufferId>| {
607 match last_reader(&epi, b) {
608 Some(r) => pops.entry(r).or_default().push(b),
609 None => after.push(b),
610 }
611 };
612 if let Some(update) = carries.get(&b) {
613 pops.entry(*update).or_default().push(b);
614 final_pop(&mut pops, &mut after_blocks);
615 continue;
616 }
617 match producer_level(b) {
618 FoldLevel::Body => {
619 if let Some(r) = last_reader(&body, b) {
620 pops.entry(r).or_default().push(b);
621 }
622 }
623 FoldLevel::Prologue
624 if last_reader(&epi, b).is_some() || last_reader(&body, b).is_some() =>
625 {
626 final_pop(&mut pops, &mut after_blocks)
627 }
628 FoldLevel::Prologue => {
629 if let Some(r) = last_reader(&pro, b) {
630 pops.entry(r).or_default().push(b);
631 }
632 }
633 FoldLevel::Epilogue => {
634 if let Some(r) = last_reader(&epi, b) {
635 pops.entry(r).or_default().push(b);
636 }
637 }
638 }
639 }
640 let none: Vec<BufferId> = Vec::new();
641 let ops_of = |stages: &[&Stage]| {
642 stages
643 .iter()
644 .flat_map(|s| stage_ops(p, s, pops.get(&s.id).unwrap_or(&none)))
645 .collect::<Vec<TaggedOp>>()
646 };
647 let mut out = ops_of(&pro);
648 let blocks = ops_of(&body);
649 let last_body = body.last().map(|s| s.id).unwrap_or(StageId::new(0));
650 if nb > 1 {
651 if let Some(first) = blocks.first().map(|t| t.stage) {
652 out.push(TaggedOp {
653 stage: first,
654 op: ChoreoOp::Loop {
655 var: LV_KBLOCK,
656 count: k_count(p, nb),
657 body: blocks,
658 },
659 });
660 }
661 } else {
662 out.extend(blocks);
663 }
664 out.extend(tagged(
666 last_body,
667 after_blocks
668 .into_iter()
669 .flat_map(|b| {
670 [
671 ChoreoOp::Wait { buf: b, pages: 1 },
672 ChoreoOp::Pop { buf: b, pages: 1 },
673 ]
674 })
675 .collect(),
676 ));
677 out.extend(ops_of(&epi));
678 out
679}
680
681fn compute_program(
683 p: &SpatialProgram,
684 here: &[&Stage],
685 rank: &[usize],
686 levels: &[FoldLevel],
687) -> Result<Vec<TaggedOp>, SpatialReject> {
688 let plan = p.work.plan.expect("tiled");
689 let mut ops = Vec::new();
690 let mut stages: Vec<&Stage> = here
691 .iter()
692 .copied()
693 .filter(|s| s.role == SiteRole::Compute)
694 .collect();
695 stages.sort_by_key(|s| rank[s.id.idx()]);
696 let stages = stages;
697 let mut chain: Vec<&Stage> = here
703 .iter()
704 .copied()
705 .filter(|s| {
706 s.role == SiteRole::Compute
707 && matches!(
708 s.kind,
709 StageKind::Compute(_) | StageKind::Scalar(_) | StageKind::AllReduce { .. }
710 )
711 && !matches!(s.kind, StageKind::Compute(ComputeOp::MatmulTiles { .. }))
712 })
713 .collect();
714 chain.sort_by_key(|s| rank[s.id.idx()]);
715 let chain = chain;
716 let chain_body = chain_ops(p, &chain);
717 let has_matmul = stages
718 .iter()
719 .any(|s| matches!(s.kind, StageKind::Compute(ComputeOp::MatmulTiles { .. })));
720 let fused = has_matmul && !chain.is_empty();
721 let fuse = |body: Vec<TaggedOp>| -> Vec<TaggedOp> {
722 if fused {
723 body.into_iter().chain(chain_body.iter().cloned()).collect()
724 } else {
725 body
726 }
727 };
728 for s in stages {
729 let tag = |op: ChoreoOp| TaggedOp { stage: s.id, op };
730 let out = s.outs[0];
731 let out_dtype = p.buffer(out).dtype;
732 match &s.kind {
733 StageKind::Compute(
734 op @ ComputeOp::MatmulTiles {
735 block,
736 sub_blocks,
737 num_blocks,
738 interm,
739 ..
740 },
741 ) => {
742 if p.work.k_trip.is_some() {
743 return Err(SpatialReject::NotLowered("a matmul whose K blocks run to a trip — its accumulate-and-spill forms peel blocks a count read from the data may not have"));
744 }
745 let ins = s.ins.clone();
746 let tiles = block.acc_tiles();
747 let nb = *num_blocks;
748 let sid = s.id;
749 if is_streaming(&plan) {
750 let k_body = tagged(
752 sid,
753 vec![
754 ChoreoOp::Wait {
755 buf: ins[0],
756 pages: 1,
757 },
758 ChoreoOp::Wait {
759 buf: ins[1],
760 pages: 1,
761 },
762 ChoreoOp::Compute {
763 op: op.clone(),
764 ins: ins.clone(),
765 acc_reload: None,
766 },
767 ChoreoOp::Pop {
768 buf: ins[0],
769 pages: 1,
770 },
771 ChoreoOp::Pop {
772 buf: ins[1],
773 pages: 1,
774 },
775 ],
776 );
777 let mut seq = vec![tag(ChoreoOp::AcquireAcc { tiles })];
778 if nb > 1 {
779 seq.push(tag(ChoreoOp::Loop {
780 var: LV_KBLOCK,
781 count: LoopCount::Const(nb),
782 body: k_body,
783 }));
784 } else {
785 seq.extend(k_body);
786 }
787 seq.extend(fuse(tagged(
788 sid,
789 vec![
790 ChoreoOp::Reserve { buf: out, pages: 1 },
791 ChoreoOp::PackAcc {
792 to: out,
793 tiles,
794 convert: out_dtype,
795 accumulate: false,
796 },
797 ChoreoOp::Push { buf: out, pages: 1 },
798 ChoreoOp::ReleaseAcc,
799 ],
800 )));
801 ops.extend(seq);
802 continue;
803 }
804 let waits = tagged(
805 sid,
806 vec![
807 ChoreoOp::Wait {
808 buf: ins[0],
809 pages: 1,
810 },
811 ChoreoOp::Wait {
812 buf: ins[1],
813 pages: 1,
814 },
815 ],
816 );
817 let pops = tagged(
818 sid,
819 vec![
820 ChoreoOp::Pop {
821 buf: ins[0],
822 pages: 1,
823 },
824 ChoreoOp::Pop {
825 buf: ins[1],
826 pages: 1,
827 },
828 ],
829 );
830 if nb > 1 && interm.is_none() {
831 let k_body = waits
841 .iter()
842 .cloned()
843 .chain(tagged(sid, sub_block_body_regs(s, op, &ins, tiles)))
844 .chain(pops.iter().cloned())
845 .collect::<Vec<_>>();
846 let mut seq = vec![tag(ChoreoOp::AcquireAcc { tiles })];
847 seq.push(tag(ChoreoOp::Loop {
848 var: LV_KBLOCK,
849 count: LoopCount::Const(nb),
850 body: k_body,
851 }));
852 seq.extend(fuse(tagged(
853 sid,
854 vec![
855 ChoreoOp::Reserve { buf: out, pages: 1 },
856 ChoreoOp::PackAcc {
857 to: out,
858 tiles,
859 convert: out_dtype,
860 accumulate: false,
861 },
862 ChoreoOp::Push { buf: out, pages: 1 },
863 ChoreoOp::ReleaseAcc,
864 ],
865 )));
866 ops.extend(seq);
867 continue;
868 }
869 if nb == 1 {
870 let body = sub_loops(
871 *sub_blocks,
872 fuse(tagged(
873 sid,
874 sub_block_body(s, op, &ins, None, out, tiles, out_dtype),
875 )),
876 );
877 ops.extend(waits.into_iter().chain(body).chain(pops));
878 } else if let Some(interm) = interm.filter(|i| p.buffer(*i).alias_of == Some(out)) {
879 let interm_dtype = p.buffer(interm).dtype;
885 let subs = sub_blocks.0 * sub_blocks.1;
886 let drain: Vec<TaggedOp> = tagged(
887 sid,
888 (0..subs)
889 .flat_map(|_| {
890 [
891 ChoreoOp::Wait {
892 buf: interm,
893 pages: 1,
894 },
895 ChoreoOp::Pop {
896 buf: interm,
897 pages: 1,
898 },
899 ]
900 })
901 .collect(),
902 );
903 let mut seq = vec![tag(ChoreoOp::Reserve {
904 buf: out,
905 pages: subs,
906 })];
907 seq.extend(waits.clone());
909 seq.extend(sub_loops(
910 *sub_blocks,
911 tagged(
912 sid,
913 sub_block_body_acc(
914 s,
915 op,
916 &ins,
917 None,
918 interm,
919 tiles,
920 interm_dtype,
921 false,
922 ),
923 ),
924 ));
925 seq.extend(drain.clone());
926 seq.extend(pops.clone());
927 if nb > 2 {
929 let mut mid = waits.clone();
930 mid.extend(sub_loops(
931 *sub_blocks,
932 tagged(
933 sid,
934 sub_block_body_acc(
935 s,
936 op,
937 &ins,
938 None,
939 interm,
940 tiles,
941 interm_dtype,
942 true,
943 ),
944 ),
945 ));
946 mid.extend(drain.clone());
947 mid.extend(pops.clone());
948 seq.push(tag(ChoreoOp::Loop {
949 var: LV_KBLOCK,
950 count: LoopCount::Const(nb - 2),
951 body: mid,
952 }));
953 }
954 seq.extend(waits);
956 seq.extend(sub_loops(
957 *sub_blocks,
958 tagged(
959 sid,
960 vec![
961 ChoreoOp::AcquireAcc { tiles },
962 ChoreoOp::Compute {
963 op: op.clone(),
964 ins: ins.clone(),
965 acc_reload: None,
966 },
967 ChoreoOp::PackAcc {
968 to: out,
969 tiles,
970 convert: out_dtype,
971 accumulate: true,
972 },
973 ChoreoOp::Push { buf: out, pages: 1 },
974 ChoreoOp::ReleaseAcc,
975 ],
976 ),
977 ));
978 seq.extend(pops);
979 ops.extend(seq);
980 } else if plan.pack_accumulate {
981 let interm = interm.expect("a multi-block plan spills");
986 let interm_dtype = p.buffer(interm).dtype;
987 let subs = sub_blocks.0 * sub_blocks.1;
988 let drain: Vec<TaggedOp> = tagged(
989 sid,
990 (0..subs)
991 .flat_map(|_| {
992 [
993 ChoreoOp::Wait {
994 buf: interm,
995 pages: 1,
996 },
997 ChoreoOp::Pop {
998 buf: interm,
999 pages: 1,
1000 },
1001 ]
1002 })
1003 .collect(),
1004 );
1005 let mut seq = Vec::new();
1006 seq.extend(waits.clone());
1008 seq.extend(sub_loops(
1009 *sub_blocks,
1010 tagged(
1011 sid,
1012 sub_block_body_acc(
1013 s,
1014 op,
1015 &ins,
1016 None,
1017 interm,
1018 tiles,
1019 interm_dtype,
1020 false,
1021 ),
1022 ),
1023 ));
1024 seq.extend(drain.clone());
1025 seq.extend(pops.clone());
1026 if nb > 3 {
1028 let mut mid = waits.clone();
1029 mid.extend(sub_loops(
1030 *sub_blocks,
1031 tagged(
1032 sid,
1033 sub_block_body_acc(
1034 s,
1035 op,
1036 &ins,
1037 None,
1038 interm,
1039 tiles,
1040 interm_dtype,
1041 true,
1042 ),
1043 ),
1044 ));
1045 mid.extend(drain.clone());
1046 mid.extend(pops.clone());
1047 seq.push(tag(ChoreoOp::Loop {
1048 var: LV_KBLOCK,
1049 count: LoopCount::Const(nb - 3),
1050 body: mid,
1051 }));
1052 }
1053 seq.extend(waits.clone());
1055 seq.extend(sub_loops(
1056 *sub_blocks,
1057 tagged(
1058 sid,
1059 sub_block_body_acc(
1060 s,
1061 op,
1062 &ins,
1063 None,
1064 interm,
1065 tiles,
1066 interm_dtype,
1067 true,
1068 ),
1069 ),
1070 ));
1071 seq.extend(pops.clone());
1072 seq.extend(waits);
1074 seq.extend(sub_loops(
1075 *sub_blocks,
1076 fuse(tagged(
1077 sid,
1078 sub_block_body(s, op, &ins, Some(interm), out, tiles, out_dtype),
1079 )),
1080 ));
1081 seq.extend(pops);
1082 ops.extend(seq);
1083 } else {
1084 let interm = interm.expect("a multi-block plan spills");
1085 let interm_dtype = p.buffer(interm).dtype;
1086 let mut seq = Vec::new();
1087 seq.extend(waits.clone());
1089 seq.extend(sub_loops(
1090 *sub_blocks,
1091 tagged(
1092 sid,
1093 sub_block_body(s, op, &ins, None, interm, tiles, interm_dtype),
1094 ),
1095 ));
1096 seq.extend(pops.clone());
1097 if nb > 2 {
1099 let mut mid = waits.clone();
1100 mid.extend(sub_loops(
1101 *sub_blocks,
1102 tagged(
1103 sid,
1104 sub_block_body(
1105 s,
1106 op,
1107 &ins,
1108 Some(interm),
1109 interm,
1110 tiles,
1111 interm_dtype,
1112 ),
1113 ),
1114 ));
1115 mid.extend(pops.clone());
1116 seq.push(tag(ChoreoOp::Loop {
1117 var: LV_KBLOCK,
1118 count: LoopCount::Const(nb - 2),
1119 body: mid,
1120 }));
1121 }
1122 seq.extend(waits);
1124 seq.extend(sub_loops(
1125 *sub_blocks,
1126 fuse(tagged(
1127 sid,
1128 sub_block_body(s, op, &ins, Some(interm), out, tiles, out_dtype),
1129 )),
1130 ));
1131 seq.extend(pops);
1132 ops.extend(seq);
1133 }
1134 }
1135 StageKind::Compute(_) | StageKind::Scalar(_) | StageKind::AllReduce { .. } => {
1136 continue;
1138 }
1139 _ => {}
1140 }
1141 }
1142 let carries = p.carries();
1144 if !fused && chain.iter().any(|s| carries.values().any(|c| *c == s.id)) {
1145 let sub =
1146 (plan.per_site_m / plan.block.osh.max(1)) * (plan.per_site_n / plan.block.osw.max(1));
1147 if sub > 1 {
1148 return Err(SpatialReject::NotLowered("a chain that carries a value across K blocks over more than one output sub-block of its item"));
1149 }
1150 ops.extend(fold_ops(p, &chain, levels, plan.inner.num_blocks));
1151 return Ok(ops);
1152 }
1153 if !chain.is_empty() && !fused {
1155 let pages = chain
1156 .iter()
1157 .flat_map(|s| s.ins.iter().map(|b| pages_per_item(p, *b, 0)))
1158 .max()
1159 .unwrap_or(1);
1160 let body = chain_body;
1161 if pages > 1 {
1162 let stage = body[0].stage;
1163 ops.push(TaggedOp {
1164 stage,
1165 op: ChoreoOp::Loop {
1166 var: LV_SUB_H,
1167 count: LoopCount::Const(pages),
1168 body,
1169 },
1170 });
1171 } else {
1172 ops.extend(body);
1173 }
1174 }
1175 Ok(ops)
1176}
1177
1178fn reader_program(
1182 p: &SpatialProgram,
1183 target: &SpatialTarget,
1184 here: &[&Stage],
1185 rank: &[usize],
1186 engine: Engine,
1187) -> Vec<TaggedOp> {
1188 let pl = p.placement.as_ref().unwrap();
1189 let mut stages: Vec<&Stage> = here
1190 .iter()
1191 .copied()
1192 .filter(|s| {
1193 pl.engine_of[s.id.idx()].engine == engine
1194 && matches!(s.kind, StageKind::Load { .. } | StageKind::Recv { .. })
1195 })
1196 .collect();
1197 stages.sort_by_key(|s| rank[s.id.idx()]);
1198 let stages = stages;
1199 if stages.is_empty() {
1200 return Vec::new();
1201 }
1202 let plan = p.work.plan;
1203 let nb = plan.map(|pl| pl.inner.num_blocks).unwrap_or(1);
1204 let sub_blocks = plan
1205 .map(|pl| {
1206 (
1207 (pl.per_site_m / pl.block.osh.max(1)).max(1),
1208 (pl.per_site_n / pl.block.osw.max(1)).max(1),
1209 )
1210 })
1211 .unwrap_or((1, 1));
1212 let uses = |s: &Stage, var: LoopVar| match &s.kind {
1213 StageKind::Load { pattern, .. } => pattern.origin.steps.iter().any(|(v, _)| *v == var),
1214 StageKind::Recv { .. } => var == LV_KBLOCK,
1215 _ => false,
1216 };
1217 let (mut k_block, mut sub_block, mut once) = (Vec::new(), Vec::new(), Vec::new());
1218 for s in &stages {
1219 let is_k = uses(s, LV_KBLOCK) || matches!(s.kind, StageKind::Recv { .. });
1220 let is_sub = uses(s, LV_SUB_H) || uses(s, LV_SUB_W);
1221 let target_vec = if is_k {
1222 &mut k_block
1223 } else if is_sub {
1224 &mut sub_block
1225 } else {
1226 &mut once
1227 };
1228 reader_block(p, target, s, target_vec);
1229 }
1230 let mut ops = once;
1234 if let Some(first) = k_block.first() {
1235 let stage = first.stage;
1236 ops.push(TaggedOp {
1237 stage,
1238 op: ChoreoOp::Loop {
1239 var: LV_KBLOCK,
1240 count: k_count(p, nb),
1241 body: k_block,
1242 },
1243 });
1244 }
1245 if !sub_block.is_empty() {
1246 ops.extend(sub_loops(sub_blocks, sub_block));
1247 }
1248 ops
1249}
1250
1251fn writer_program(
1253 p: &SpatialProgram,
1254 here: &[&Stage],
1255 rank: &[usize],
1256 engine: Engine,
1257) -> Vec<TaggedOp> {
1258 let pl = p.placement.as_ref().unwrap();
1259 let mut ops = Vec::new();
1260 let mut engine_stages: Vec<&Stage> = here
1261 .iter()
1262 .copied()
1263 .filter(|s| pl.engine_of[s.id.idx()].engine == engine)
1264 .collect();
1265 engine_stages.sort_by_key(|s| rank[s.id.idx()]);
1266 for s in engine_stages {
1267 if let StageKind::Store {
1268 tensor,
1269 pattern,
1270 sub_blocks,
1271 } = &s.kind
1272 {
1273 let buf = s.ins[0];
1274 let index = pattern.origin.indirect.map(|i| i.index);
1276 let mut body = vec![ChoreoOp::Wait { buf, pages: 1 }];
1277 if let Some(ix) = index {
1278 body.push(ChoreoOp::Wait { buf: ix, pages: 1 });
1279 }
1280 body.extend([
1281 ChoreoOp::WriteTiles {
1282 from: buf,
1283 tensor: *tensor,
1284 at: pattern.origin.clone(),
1285 block_h: pattern.block_h,
1286 block_w: pattern.block_w,
1287 stride_w: pattern.stride_w,
1288 stride_h: pattern.stride_h,
1289 },
1290 ChoreoOp::WriteBarrier,
1291 ChoreoOp::Pop { buf, pages: 1 },
1292 ]);
1293 if let Some(ix) = index {
1294 body.push(ChoreoOp::Pop { buf: ix, pages: 1 });
1295 }
1296 ops.extend(sub_loops(*sub_blocks, tagged(s.id, body)));
1297 }
1298 }
1299 ops
1300}
1301
1302pub fn synthesize_handshakes(
1304 p: &SpatialProgram,
1305 target: &SpatialTarget,
1306) -> Result<SpatialProgram, SpatialReject> {
1307 if p.schedule.is_some() {
1308 return Ok(p.clone());
1309 }
1310 let Some(pl) = &p.placement else {
1311 return Err(SpatialReject::WrongPhase {
1312 pass: "synthesize_handshakes",
1313 expected: "Sized",
1314 found: p.phase(),
1315 });
1316 };
1317 if p.memory.is_none() {
1318 return Err(SpatialReject::WrongPhase {
1319 pass: "synthesize_handshakes",
1320 expected: "Sized",
1321 found: p.phase(),
1322 });
1323 }
1324 let local = crate::spatial::passes::from_tensor_op::local_handshake(target)?;
1327 let mut p = p.clone();
1328 for e in &mut p.edges {
1329 if e.handshake == Handshake::Fifo && local != Handshake::Fifo {
1330 e.handshake = local.clone();
1331 }
1332 }
1333 p.requires.handshakes = p.edges.iter().map(|e| e.handshake.kind()).collect();
1334 let p = &p;
1335 for e in &p.edges {
1336 let k = e.handshake.kind();
1337 if !target.provides_handshake(k) {
1338 return Err(SpatialReject::MissingHandshake {
1339 needed: k,
1340 target: target.name,
1341 });
1342 }
1343 }
1344 let rank = stage_ranks(p);
1347 let levels = if p.carries().is_empty() {
1348 vec![FoldLevel::Prologue; p.stages.len()]
1349 } else {
1350 p.fold_levels()
1351 };
1352 let mut on_site: BTreeMap<Site, Vec<&Stage>> = BTreeMap::new();
1353 for s in &p.stages {
1354 if let Some(site) = s.site {
1355 on_site.entry(site).or_default().push(s);
1356 }
1357 }
1358 let mut programs = Vec::new();
1359 let sites: Vec<Site> = pl.items_of_site.iter().map(|(s, _)| *s).collect();
1360 for site in sites {
1361 let here: &[&Stage] = on_site.get(&site).map(Vec::as_slice).unwrap_or(&[]);
1362 let mut engines: Vec<Engine> = here
1363 .iter()
1364 .map(|s| pl.engine_of[s.id.idx()].engine)
1365 .collect();
1366 engines.sort();
1367 engines.dedup();
1368 for engine in engines {
1369 let ops = match engine {
1370 Engine::Compute => compute_program(p, here, &rank, &levels)?,
1371 Engine::Movement(_) => {
1372 let mut ops = reader_program(p, target, here, &rank, engine);
1373 ops.extend(writer_program(p, here, &rank, engine));
1374 fuse_adjacent_loops(ops)
1375 }
1376 };
1377 if ops.is_empty() {
1378 continue;
1379 }
1380 let stages: Vec<StageId> = here
1381 .iter()
1382 .filter(|s| pl.engine_of[s.id.idx()].engine == engine)
1383 .map(|s| s.id)
1384 .collect();
1385 programs.push(EngineProgram {
1386 site,
1387 engine,
1388 stages,
1389 ops,
1390 });
1391 }
1392 }
1393 let mut q = p.clone();
1394 q.schedule = Some(Schedule { programs });
1395 q.validate()?;
1396 Ok(q)
1397}