logicaffeine_compile/spatial/passes/
fuse_readers.rs1use super::SpatialReject;
11use crate::spatial::cost::is_streaming;
12use crate::spatial::ir::*;
13use crate::spatial::target::{HandshakeKind, Interconnect, SpatialTarget};
14
15pub fn fusion_wins(p: &SpatialProgram, target: &SpatialTarget) -> bool {
17 let Some(plan) = p.work.plan else {
18 return false;
19 };
20 if is_streaming(&plan) {
21 return false;
22 }
23 if !target.provides_interconnect(Interconnect::MulticastRect) {
31 return false;
32 }
33 let (ny, nx) = plan.grid;
34 if nx <= 1 && ny <= 1 {
35 return false;
36 }
37 if ny > target.sites.y || nx > target.sites.x {
42 return false;
43 }
44 let objective = crate::spatial::work::Objective::default();
49 let (Some(fused), Some(unfused)) = (
50 crate::spatial::work::work_of_plan(p, &plan, true, target),
51 crate::spatial::work::work_of_plan(p, &plan, false, target),
52 ) else {
53 return false;
54 };
55 objective.key(&fused) < objective.key(&unfused)
56}
57
58pub fn fuse_readers(
60 p: &SpatialProgram,
61 target: &SpatialTarget,
62) -> Result<SpatialProgram, SpatialReject> {
63 let has_matmul = p
64 .stages
65 .iter()
66 .any(|s| matches!(s.kind, StageKind::Compute(ComputeOp::MatmulTiles { .. })));
67 fuse_readers_forced(p, target, has_matmul && fusion_wins(p, target))
68}
69
70pub fn fuse_readers_forced(
72 p: &SpatialProgram,
73 target: &SpatialTarget,
74 fused: bool,
75) -> Result<SpatialProgram, SpatialReject> {
76 let Some(pl) = &p.placement else {
77 return Err(SpatialReject::WrongPhase {
78 pass: "fuse_readers",
79 expected: "Placed",
80 found: p.phase(),
81 });
82 };
83 if !pl.groups.is_empty() || !fused {
84 return Ok(p.clone());
85 }
86 if !target.provides_interconnect(Interconnect::MulticastRect) {
87 return Err(SpatialReject::MissingInterconnect {
88 needed: Interconnect::MulticastRect,
89 target: target.name,
90 });
91 }
92 if !target.provides_handshake(HandshakeKind::Semaphore) {
93 return Err(SpatialReject::MissingHandshake {
94 needed: HandshakeKind::Semaphore,
95 target: target.name,
96 });
97 }
98 let plan = p.work.plan.expect("placed programs are tiled");
99 let (ny, nx) = plan.grid;
100 let rule = target.noc_rule;
101 let mut q = p.clone();
102 let pl = q.placement.as_mut().unwrap();
103 let sites: Vec<Site> = pl.items_of_site.iter().map(|(s, _)| *s).collect();
104 let mut groups = Vec::new();
105 let mut specs: Vec<(Direction, Operand, Rect, Site)> = Vec::new();
107 if nx > 1 {
108 for y in 0..ny {
109 let rect = Rect {
110 x0: 0,
111 y0: y as u16,
112 x1: (nx - 1) as u16,
113 y1: y as u16,
114 };
115 specs.push((Direction::Row, Operand::A, rect, Site::new(0, y)));
116 }
117 }
118 if ny > 1 {
119 for x in 0..nx {
120 let rect = Rect {
121 x0: x as u16,
122 y0: 0,
123 x1: x as u16,
124 y1: (ny - 1) as u16,
125 };
126 specs.push((Direction::Col, Operand::B, rect, Site::new(x, 0)));
127 }
128 }
129 let mut sems_of: std::collections::BTreeMap<Operand, (SemId, SemId)> = Default::default();
130 for (direction, operand, rect, sender) in specs {
131 let members = rect.sites();
132 if !members.iter().all(|m| sites.contains(m)) {
133 continue;
134 }
135 let counts: std::collections::BTreeSet<usize> =
136 members.iter().map(|m| pl.items_at(*m).len()).collect();
137 if counts.len() != 1 {
138 return Err(SpatialReject::GroupCountMismatch {
139 group: GroupId::new(groups.len()),
140 counts: members
141 .iter()
142 .map(|m| (*m, pl.items_at(*m).len()))
143 .collect(),
144 });
145 }
146 let gid = GroupId::new(groups.len());
147 let noc = NocId(rule.map(|r| r.sender_noc).unwrap_or(0));
148 groups.push(Group {
149 id: gid,
150 rect,
151 sender,
152 direction,
153 operand,
154 noc,
155 });
156 let tensor = match operand {
157 Operand::A => TensorId(0),
158 Operand::B => TensorId(1),
159 };
160 let (ready, valid) = match sems_of.get(&operand) {
161 Some(pair) => *pair,
162 None => {
163 let ready = SemId::new(q.semaphores.len());
164 q.semaphores.push(Semaphore {
165 id: ready,
166 init: 0,
167 role: SemRole::ReadyCounter {
168 expected: members.len() - 1,
169 },
170 operand: Some(operand),
171 sites: Vec::new(),
172 });
173 let valid = SemId::new(q.semaphores.len());
174 q.semaphores.push(Semaphore {
175 id: valid,
176 init: 0,
177 role: SemRole::ValidFlag,
178 operand: Some(operand),
179 sites: Vec::new(),
180 });
181 sems_of.insert(operand, (ready, valid));
182 (ready, valid)
183 }
184 };
185 for sem in [ready, valid] {
186 for m in &members {
187 if !q.semaphores[sem.idx()].sites.contains(m) {
188 q.semaphores[sem.idx()].sites.push(*m);
189 }
190 }
191 }
192 let mut sender_stage = None;
193 let mut sender_buf = None;
194 let mut receivers = Vec::new();
195 for s in q.stages.iter_mut() {
196 let Some(site) = s.site else { continue };
197 if !rect.contains(site) {
198 continue;
199 }
200 let is_load = matches!(&s.kind, StageKind::Load { tensor: t, .. } if *t == tensor);
201 if !is_load {
202 continue;
203 }
204 if site == sender {
205 if let StageKind::Load { multicast, .. } = &mut s.kind {
206 *multicast = Some(gid);
207 }
208 sender_stage = Some(s.id);
209 sender_buf = s.outs.first().copied();
210 } else {
211 s.kind = StageKind::Recv { group: gid };
212 s.name = s.name.replace("load_", "recv_");
213 s.ins.clear();
214 receivers.push(s.id);
215 }
216 }
217 let (Some(from), Some(buffer)) = (sender_stage, sender_buf) else {
218 return Err(SpatialReject::Invalid(IrViolation::GroupMalformed {
219 group: gid,
220 reason: "no sender load stage",
221 }));
222 };
223 q.edges.push(Edge {
224 id: EdgeId::new(q.edges.len()),
225 from,
226 buffer,
227 to: receivers,
228 handshake: Handshake::Semaphore {
229 ready,
230 valid,
231 ready_count: members.len() - 1,
232 },
233 });
234 }
235 let pl = q.placement.as_mut().unwrap();
236 pl.groups = groups;
237 if let Some(r) = rule {
247 let groups = pl.groups.clone();
248 let a_senders: std::collections::BTreeSet<Site> = groups
249 .iter()
250 .filter(|g| g.operand == Operand::A)
251 .map(|g| g.sender)
252 .collect();
253 for (i, s) in q.stages.iter().enumerate() {
254 let Some(site) = s.site else { continue };
255 let noc = match &s.kind {
256 StageKind::Load {
257 multicast: Some(g), ..
258 } if groups[g.idx()].direction == Direction::Row => r.sender_noc,
259 StageKind::Load { .. } | StageKind::Recv { .. } => {
260 if a_senders.contains(&site) {
261 r.sender_noc
262 } else {
263 r.receiver_noc
264 }
265 }
266 StageKind::Store { .. } => {
267 if site.x == 0 {
268 r.writer_noc_first_column
269 } else {
270 r.writer_noc_default
271 }
272 }
273 _ => continue,
274 };
275 pl.engine_of[i].noc = Some(NocId(noc));
276 }
277 let mut taken: std::collections::BTreeMap<(Site, NocId), Engine> = Default::default();
292 for slot in pl.engine_of.iter_mut() {
293 let Some(noc) = slot.noc else { continue };
294 match taken.get(&(slot.site, noc)) {
295 Some(e) if *e != slot.engine => {
296 let other = NocId(1 - noc.0);
297 slot.noc = Some(other);
298 taken.insert((slot.site, other), slot.engine);
299 }
300 _ => {
301 taken.insert((slot.site, noc), slot.engine);
302 }
303 }
304 }
305 }
306 q.requires.handshakes.insert(HandshakeKind::Semaphore);
307 q.requires.interconnect.insert(Interconnect::MulticastRect);
308 q.validate()?;
309 Ok(q)
310}