logicaffeine_compile/spatial/passes/
forward.rs1use crate::spatial::ir::*;
28
29#[derive(Clone, Debug)]
31pub struct Joint {
32 pub program: SpatialProgram,
34 pub carrier: BufferId,
36}
37
38pub fn one_block(p: &SpatialProgram) -> bool {
41 p.work.items.len() == 1
42 && p.stages.iter().all(|s| match &s.kind {
43 StageKind::Compute(ComputeOp::MatmulTiles { sub_blocks, .. }) => *sub_blocks == (1, 1),
44 _ => true,
45 })
46}
47
48pub fn forward(first: &SpatialProgram, second: &SpatialProgram, tensor: &str) -> Option<Joint> {
51 let tiled = match (first.phase(), second.phase()) {
54 (Phase::Template, Phase::Template) => false,
55 (Phase::Tiled, Phase::Tiled) if second.trips.is_empty() && second.work.k_trip.is_none() => true,
56 _ => return None,
57 };
58 if first.tile != second.tile
61 || first.work.items != second.work.items
62 || (second.work.k_tiles != 1 && second.work.k_tiles != first.work.k_tiles)
63 || first.mem_banks != second.mem_banks
64 || first.numerics != second.numerics
65 {
66 return None;
67 }
68 let t1 = first.tensors.iter().find(|t| t.name == tensor)?;
69 let t2 = second.tensors.iter().find(|t| t.name == tensor)?;
70 if (t1.elems, t1.tiles, t1.dtype, t1.layout) != (t2.elems, t2.tiles, t2.dtype, t2.layout) {
71 return None;
72 }
73 let stores: Vec<&Stage> = first.stages.iter().filter(|s| matches!(s.kind, StageKind::Store { .. })).collect();
75 let [store] = stores.as_slice() else { return None };
76 let StageKind::Store { tensor: st, pattern: sp, sub_blocks } = &store.kind else { return None };
77 if *st != t1.id || store.ins.len() != 1 {
78 return None;
79 }
80 let o = store.ins[0];
81 if first.stages.iter().any(|s| s.id != store.id && s.ins.contains(&o)) {
82 return None;
83 }
84 let names = |s: &Stage, t: TensorId| {
86 matches!(&s.kind, StageKind::Load { tensor, .. } | StageKind::Store { tensor, .. } | StageKind::AllReduce { tensor, .. } if *tensor == t)
87 };
88 let loads: Vec<&Stage> = second
90 .stages
91 .iter()
92 .filter(|s| matches!(&s.kind, StageKind::Load { tensor: x, .. } if *x == t2.id))
93 .collect();
94 let [load] = loads.as_slice() else { return None };
95 if first.stages.iter().any(|s| s.id != store.id && names(s, t1.id)) || second.stages.iter().any(|s| s.id != load.id && names(s, t2.id)) {
96 return None;
97 }
98 let StageKind::Load { pattern: lp, num_blocks, multicast, .. } = &load.kind else { return None };
99 if *num_blocks != 1 || multicast.is_some() || load.outs.len() != 1 || first.work.items.is_empty() {
100 return None;
101 }
102 for item in &first.work.items {
104 let stored = sp.tiles_at(item.coord, *sub_blocks)?;
105 let loaded = lp.tiles_at(item.coord, (1, 1))?;
106 if stored != loaded {
107 return None;
108 }
109 }
110 let one_block = !sp.origin.steps.is_empty();
115 if one_block && !tiled && first.work.items.len() != 1 {
116 return None;
117 }
118 let i = load.outs[0];
119 let readers: Vec<&Stage> = second.stages.iter().filter(|s| s.ins.contains(&i)).collect();
120 if readers.is_empty() || !readers.iter().all(|s| matches!(s.kind, StageKind::Compute(_))) {
121 return None;
122 }
123 let (bo, bi) = (first.buffer(o), second.buffer(i));
124 if bo.kind != bi.kind || bo.dtype != bi.dtype {
125 return None;
126 }
127
128 let mut tensors: Vec<TensorDecl> = Vec::new();
131 let mut tmap1 = std::collections::BTreeMap::new();
132 for t in first.tensors.iter().filter(|t| t.id != t1.id) {
133 tmap1.insert(t.id, TensorId::new(tensors.len()));
134 tensors.push(TensorDecl { id: TensorId::new(tensors.len()), ..t.clone() });
135 }
136 let mut tmap2 = std::collections::BTreeMap::new();
137 for t in second.tensors.iter().filter(|t| t.id != t2.id) {
138 match tensors.iter().find(|x| x.name == t.name) {
139 Some(x) => {
140 if (TensorDecl { id: x.id, ..t.clone() }) != *x {
141 return None;
142 }
143 tmap2.insert(t.id, x.id);
144 }
145 None => {
146 tmap2.insert(t.id, TensorId::new(tensors.len()));
147 tensors.push(TensorDecl { id: TensorId::new(tensors.len()), ..t.clone() });
148 }
149 }
150 }
151
152 let mut buffers: Vec<Buffer> = first.buffers.clone();
154 let mut bmap2 = std::collections::BTreeMap::new();
155 bmap2.insert(i, o);
156 for b in second.buffers.iter().filter(|b| b.id != i) {
157 let mut name = b.name.clone();
159 let mut k = 1;
160 while buffers.iter().any(|x| x.name == name) {
161 name = format!("{}_f{k}", b.name);
162 k += 1;
163 }
164 bmap2.insert(b.id, BufferId::new(buffers.len()));
165 buffers.push(Buffer { id: BufferId::new(buffers.len()), name, ..b.clone() });
166 }
167
168 let mut stages: Vec<Stage> = Vec::new();
170 let mut smap1 = std::collections::BTreeMap::new();
171 for s in first.stages.iter().filter(|s| s.id != store.id) {
172 let id = StageId::new(stages.len());
173 smap1.insert(s.id, id);
174 let mut kind = s.kind.clone();
175 retensor(&mut kind, &tmap1);
176 stages.push(Stage { id, kind, ..s.clone() });
177 }
178 let mut smap2 = std::collections::BTreeMap::new();
179 for s in second.stages.iter().filter(|s| s.id != load.id) {
180 let id = StageId::new(stages.len());
181 smap2.insert(s.id, id);
182 let mut kind = s.kind.clone();
183 retensor(&mut kind, &tmap2);
184 kind.remap_buffers(&|b| bmap2[&b]);
185 stages.push(Stage {
186 id,
187 kind,
188 ins: s.ins.iter().map(|b| bmap2[b]).collect(),
189 outs: s.outs.iter().map(|b| bmap2[b]).collect(),
190 ..s.clone()
191 });
192 }
193
194 let mut edges: Vec<Edge> = Vec::new();
196 for e in &first.edges {
197 let to: Vec<StageId> = if e.buffer == o {
198 readers.iter().map(|s| smap2[&s.id]).collect()
199 } else {
200 e.to.iter().map(|s| smap1[s]).collect()
201 };
202 edges.push(Edge { id: EdgeId::new(edges.len()), from: smap1[&e.from], to, ..e.clone() });
203 }
204 for e in second.edges.iter().filter(|e| e.buffer != i) {
205 edges.push(Edge {
206 id: EdgeId::new(edges.len()),
207 from: smap2[&e.from],
208 buffer: bmap2[&e.buffer],
209 to: e.to.iter().map(|s| smap2[s]).collect(),
210 ..e.clone()
211 });
212 }
213
214 let claim = match (&first.claim, &second.claim) {
215 (NumericsClaim::Exact, c) | (c, NumericsClaim::Exact) => c.clone(),
216 (c, _) => c.clone(),
217 };
218 let mut requires = first.requires.clone();
219 requires.handshakes.extend(second.requires.handshakes.iter().copied());
220 requires.interconnect.extend(second.requires.interconnect.iter().copied());
221 let program = SpatialProgram {
222 name: format!("{}+{}", first.name, second.name),
223 tensors,
224 stages,
225 buffers,
226 edges,
227 claim,
228 requires,
229 acc_tiles_used: first.acc_tiles_used.max(second.acc_tiles_used),
230 ..first.clone()
231 };
232 if tiled && one_block && !self::one_block(&program) {
234 return None;
235 }
236 Some(Joint { program, carrier: o })
237}
238
239fn retensor(kind: &mut StageKind, map: &std::collections::BTreeMap<TensorId, TensorId>) {
241 match kind {
242 StageKind::Load { tensor, .. } | StageKind::Store { tensor, .. } | StageKind::AllReduce { tensor, .. } => *tensor = map[tensor],
243 StageKind::Recv { .. } | StageKind::Compute(_) | StageKind::Scalar(_) => {}
244 }
245}