1use std::collections::{BTreeMap, BTreeSet, HashMap};
19
20use logicaffeine_language::ast::stmt::{Expr, Literal, Stmt};
21
22use crate::spatial::passes::SpatialReject;
23use crate::intern::{Interner, Symbol};
24use crate::tensor_recognize::qwen::{
25 recognize_argmax, recognize_attention, recognize_causal_attention, recognize_causal_conv,
26 recognize_decay, recognize_full_attention, recognize_gated_delta, recognize_gelu, recognize_head_slice,
27 recognize_int_clamp, recognize_int_requant, recognize_join, recognize_l2norm, recognize_lut_attention, recognize_moe,
28 recognize_relu, recognize_rope, recognize_row_gather, recognize_rows_gather, recognize_scale, recognize_set_row,
29 recognize_sigmoid, recognize_silu, recognize_softcap, recognize_transpose, ArgMaxSpec, AttentionSpec, CausalConvSpec,
30 DecaySpec,
31 FullAttentionSpec, GatedDeltaSpec, GeluSpec, HeadSliceSpec, IntClampSpec, IntRequantSpec, JoinSpec, L2NormSpec,
32 LutAttentionSpec, MoeSpec, ReluSpec, RopeSpec, RowGatherSpec, RowIndex, RowsGatherSpec, ScaleSpec, SetRowSpec,
33 SigmoidSpec, SiluSpec, SoftcapSpec, TransposeSpec,
34};
35use crate::tensor_recognize::{
36 recognize_elementwise, recognize_fused_row, recognize_head_matmul, recognize_matmul, Fills,
37 recognize_swiglu, ElementwiseSpec, Fill, FusedRowSpec, HeadMatmulSpec, MatmulSpec, SwiGluSpec,
38};
39
40#[derive(Clone, Debug)]
43pub enum TensorOpSpec {
44 Matmul(MatmulSpec),
45 Elementwise(ElementwiseSpec),
46 SwiGlu(SwiGluSpec),
47 FusedRow(FusedRowSpec),
48 Rope(RopeSpec),
49 Silu(SiluSpec),
50 Sigmoid(SigmoidSpec),
51 Gelu(GeluSpec),
52 Scale(ScaleSpec),
53 Softcap(SoftcapSpec),
54 Decay(DecaySpec),
55 L2Norm(L2NormSpec),
56 Transpose(TransposeSpec),
57 HeadSlice(HeadSliceSpec),
58 Join(JoinSpec),
59 HeadMatmul(HeadMatmulSpec),
60 CausalConv(CausalConvSpec),
61 GatedDelta(GatedDeltaSpec),
62 Attention(AttentionSpec),
63 FullAttention(FullAttentionSpec),
64 Moe(MoeSpec),
65 RowGather(RowGatherSpec),
66 RowsGather(RowsGatherSpec),
67 ArgMax(ArgMaxSpec),
68 SetRow(SetRowSpec),
69 IntRequant(IntRequantSpec),
70 IntClamp(IntClampSpec),
71 Relu(ReluSpec),
72 LutAttention(LutAttentionSpec),
73}
74
75impl TensorOpSpec {
76 pub fn kind(&self) -> &'static str {
78 match self {
79 TensorOpSpec::Matmul(_) => "Matmul",
80 TensorOpSpec::Elementwise(_) => "Elementwise",
81 TensorOpSpec::SwiGlu(_) => "SwiGlu",
82 TensorOpSpec::FusedRow(_) => "FusedRow",
83 TensorOpSpec::Rope(_) => "Rope",
84 TensorOpSpec::Silu(_) => "Silu",
85 TensorOpSpec::Sigmoid(_) => "Sigmoid",
86 TensorOpSpec::Gelu(_) => "Gelu",
87 TensorOpSpec::Scale(_) => "Scale",
88 TensorOpSpec::Softcap(_) => "Softcap",
89 TensorOpSpec::Decay(_) => "Decay",
90 TensorOpSpec::L2Norm(_) => "L2Norm",
91 TensorOpSpec::Transpose(_) => "Transpose",
92 TensorOpSpec::HeadSlice(_) => "HeadSlice",
93 TensorOpSpec::Join(_) => "Join",
94 TensorOpSpec::HeadMatmul(_) => "HeadMatmul",
95 TensorOpSpec::CausalConv(_) => "CausalConv",
96 TensorOpSpec::GatedDelta(_) => "GatedDelta",
97 TensorOpSpec::Attention(_) => "Attention",
98 TensorOpSpec::FullAttention(_) => "FullAttention",
99 TensorOpSpec::Moe(_) => "Moe",
100 TensorOpSpec::RowGather(_) => "RowGather",
101 TensorOpSpec::RowsGather(_) => "RowsGather",
102 TensorOpSpec::ArgMax(_) => "ArgMax",
103 TensorOpSpec::SetRow(_) => "SetRow",
104 TensorOpSpec::IntRequant(_) => "IntRequant",
105 TensorOpSpec::IntClamp(_) => "IntClamp",
106 TensorOpSpec::Relu(_) => "Relu",
107 TensorOpSpec::LutAttention(_) => "LutAttention",
108 }
109 }
110
111 pub fn out(&self) -> Symbol {
113 match self {
114 TensorOpSpec::Matmul(m) => m.c,
115 TensorOpSpec::Elementwise(e) => e.c,
116 TensorOpSpec::SwiGlu(s) => s.y,
117 TensorOpSpec::FusedRow(r) => r.y,
118 TensorOpSpec::Rope(r) => r.out,
119 TensorOpSpec::Silu(s) => s.out,
120 TensorOpSpec::Sigmoid(s) => s.out,
121 TensorOpSpec::Gelu(g) => g.out,
122 TensorOpSpec::Scale(s) => s.out,
123 TensorOpSpec::Softcap(s) => s.out,
124 TensorOpSpec::Decay(d) => d.out,
125 TensorOpSpec::L2Norm(l) => l.out,
126 TensorOpSpec::Transpose(t) => t.out,
127 TensorOpSpec::HeadSlice(s) => s.out,
128 TensorOpSpec::Join(j) => j.out,
129 TensorOpSpec::HeadMatmul(m) => m.out,
130 TensorOpSpec::CausalConv(c) => c.out,
131 TensorOpSpec::GatedDelta(g) => g.out,
132 TensorOpSpec::Attention(a) => a.out,
133 TensorOpSpec::FullAttention(a) => a.out,
134 TensorOpSpec::Moe(m) => m.out,
135 TensorOpSpec::RowGather(r) => r.out,
136 TensorOpSpec::RowsGather(r) => r.out,
137 TensorOpSpec::ArgMax(a) => a.out,
138 TensorOpSpec::SetRow(r) => r.table,
139 TensorOpSpec::IntRequant(r) => r.out,
140 TensorOpSpec::IntClamp(c) => c.out,
141 TensorOpSpec::Relu(r) => r.out,
142 TensorOpSpec::LutAttention(a) => a.out,
143 }
144 }
145
146 pub fn ins(&self) -> Vec<Symbol> {
154 match self {
155 TensorOpSpec::Matmul(m) => vec![m.a, m.b],
156 TensorOpSpec::Elementwise(e) => vec![e.a, e.b],
157 TensorOpSpec::SwiGlu(s) => vec![s.gate, s.up],
158 TensorOpSpec::FusedRow(r) => std::iter::once(r.x)
159 .chain(r.weight)
160 .chain(r.residual)
161 .chain(r.bias)
162 .collect(),
163 TensorOpSpec::Rope(r) => [r.q, r.rc, r.rs]
164 .into_iter()
165 .chain(r.from.and_then(|(p, _)| named(p)))
166 .collect(),
167 TensorOpSpec::Silu(s) => vec![s.x],
168 TensorOpSpec::Sigmoid(s) => vec![s.x],
169 TensorOpSpec::Gelu(g) => vec![g.x],
170 TensorOpSpec::Scale(s) => vec![s.x],
171 TensorOpSpec::Softcap(s) => vec![s.x],
172 TensorOpSpec::Decay(d) => vec![d.ar, d.dt, d.alog],
173 TensorOpSpec::L2Norm(l) => vec![l.x],
174 TensorOpSpec::Transpose(t) => vec![t.x],
175 TensorOpSpec::HeadSlice(s) => vec![s.x],
176 TensorOpSpec::Join(j) => vec![j.a, j.b],
177 TensorOpSpec::HeadMatmul(m) => vec![m.x, m.w],
178 TensorOpSpec::CausalConv(c) => vec![c.x, c.wc, c.cs],
179 TensorOpSpec::GatedDelta(g) => vec![g.q, g.k, g.v, g.z, g.b, g.a, g.st, g.gn],
180 TensorOpSpec::Attention(a) => [a.q, a.kc, a.vc]
181 .into_iter()
182 .chain(a.pos.and_then(named))
183 .chain(a.sinks)
184 .collect(),
185 TensorOpSpec::FullAttention(a) => vec![a.q, a.k, a.v],
186 TensorOpSpec::Moe(m) => [m.x, m.wr, m.wg, m.wu, m.wd]
187 .into_iter()
188 .chain(m.router_bias)
189 .chain(m.biases.into_iter().flatten())
190 .chain(m.selection_bias)
191 .collect(),
192 TensorOpSpec::RowGather(r) => std::iter::once(r.table).chain(named(r.index)).collect(),
193 TensorOpSpec::RowsGather(r) => vec![r.table, r.indices],
194 TensorOpSpec::ArgMax(a) => vec![a.x],
195 TensorOpSpec::SetRow(r) => [r.table, r.source]
196 .into_iter()
197 .chain(named(r.index))
198 .collect(),
199 TensorOpSpec::IntRequant(r) => vec![r.x],
200 TensorOpSpec::IntClamp(c) => vec![c.x],
201 TensorOpSpec::Relu(r) => vec![r.x],
202 TensorOpSpec::LutAttention(a) => [a.q, a.kc, a.vc, a.table]
203 .into_iter()
204 .chain(a.pos.and_then(named))
205 .collect(),
206 }
207 }
208
209 pub fn state_inputs(&self) -> Vec<usize> {
213 match self {
214 TensorOpSpec::CausalConv(_) => vec![2],
215 TensorOpSpec::GatedDelta(_) => vec![6],
216 TensorOpSpec::SetRow(_) => vec![0],
217 _ => Vec::new(),
218 }
219 }
220
221 pub fn assembled_rows(&self) -> Option<usize> {
225 match self {
226 TensorOpSpec::GatedDelta(g) if g.rows > 1 => Some(g.rows),
227 _ => None,
228 }
229 }
230}
231
232#[derive(Clone, Debug)]
235pub struct RecognizedStage {
236 pub op: TensorOpSpec,
237 pub stmt_index: usize,
238 pub out: String,
239 pub ins: Vec<String>,
240}
241
242#[derive(Clone, Debug, PartialEq, Eq)]
245pub struct DeclaredTensor {
246 pub name: String,
247 pub len: usize,
248 pub fill: Fill,
249}
250
251#[derive(Clone, Debug, Default)]
254pub struct RecognizedProgram {
255 pub stages: Vec<RecognizedStage>,
256 pub unrecognized: Vec<(usize, String)>,
257 pub unlifted_writes: Vec<(usize, String, &'static str)>,
260 pub declared: Vec<DeclaredTensor>,
261 pub symbols: HashMap<Symbol, String>,
262 pub shapes: HashMap<String, (usize, usize)>,
265 pub mentions: HashMap<String, std::collections::BTreeSet<usize>>,
268 pub opaque: Vec<usize>,
270 pub quiet: BTreeSet<usize>,
273}
274
275impl RecognizedProgram {
276 pub fn shape_of(&self, name: &str, n: usize) -> (usize, usize) {
279 match self.shapes.get(name) {
280 Some(&(r, c)) if r * c == n => (r, c),
281 _ => (1, n),
282 }
283 }
284
285 pub fn name(&self, s: Symbol) -> &str {
287 self.symbols
288 .get(&s)
289 .map(String::as_str)
290 .unwrap_or_else(|| panic!("{s:?} is not a symbol of this program's specs"))
291 }
292}
293
294pub fn float_tensors(rp: &RecognizedProgram) -> BTreeSet<String> {
302 let mut float: BTreeSet<String> = rp
303 .declared
304 .iter()
305 .filter(|d| matches!(d.fill, Fill::ConstF(_) | Fill::FloatOverrides { .. } | Fill::RealPattern { .. }))
306 .map(|d| d.name.clone())
307 .collect();
308 for st in &rp.stages {
309 let is_float = match st.op {
310 TensorOpSpec::ArgMax(_) => false,
312 TensorOpSpec::RowGather(_) | TensorOpSpec::RowsGather(_) | TensorOpSpec::SetRow(_) => float.contains(&st.ins[0]),
314 TensorOpSpec::Matmul(_)
315 | TensorOpSpec::Elementwise(_)
316 | TensorOpSpec::Transpose(_)
317 | TensorOpSpec::HeadSlice(_)
318 | TensorOpSpec::Join(_)
319 | TensorOpSpec::HeadMatmul(_) => st.ins.iter().any(|n| float.contains(n)),
320 TensorOpSpec::IntRequant(_) | TensorOpSpec::IntClamp(_) | TensorOpSpec::LutAttention(_) => false,
322 TensorOpSpec::Relu(ref r) => !r.int,
323 _ => true,
324 };
325 if is_float {
326 float.insert(st.out.clone());
327 } else {
328 float.remove(&st.out);
329 }
330 }
331 float
332}
333
334#[derive(Clone, Debug, PartialEq, Eq)]
337pub struct Link {
338 pub producer: usize,
339 pub consumers: Vec<usize>,
340 pub tensor: String,
341}
342
343fn named(index: RowIndex) -> Option<Symbol> {
345 match index {
346 RowIndex::Named(s) => Some(s),
347 RowIndex::Literal(_) => None,
348 }
349}
350
351type Recognizer = fn(&[Stmt], &Interner) -> Option<TensorOpSpec>;
352
353fn r_moe(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
354 recognize_moe(s).map(TensorOpSpec::Moe)
355}
356fn r_attention(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
357 recognize_attention(s)
358 .or_else(|| recognize_causal_attention(s))
359 .map(TensorOpSpec::Attention)
360}
361fn r_lut_attention(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
362 recognize_lut_attention(s, i).map(TensorOpSpec::LutAttention)
363}
364fn r_int_requant(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
365 recognize_int_requant(s, i).map(TensorOpSpec::IntRequant)
366}
367fn r_int_clamp(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
368 recognize_int_clamp(s, i).map(TensorOpSpec::IntClamp)
369}
370fn r_relu(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
371 recognize_relu(s, i).map(TensorOpSpec::Relu)
372}
373fn r_full_attention(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
374 recognize_full_attention(s).map(TensorOpSpec::FullAttention)
375}
376fn r_row_gather(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
377 recognize_row_gather(s).map(TensorOpSpec::RowGather)
378}
379fn r_rows_gather(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
380 recognize_rows_gather(s).map(TensorOpSpec::RowsGather)
381}
382fn r_argmax(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
383 recognize_argmax(s).map(TensorOpSpec::ArgMax)
384}
385fn r_set_row(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
386 recognize_set_row(s).map(TensorOpSpec::SetRow)
387}
388fn r_matmul(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
389 recognize_matmul(s, i).ok().map(TensorOpSpec::Matmul)
390}
391fn r_elementwise(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
392 recognize_elementwise(s, i)
393 .ok()
394 .map(TensorOpSpec::Elementwise)
395}
396fn r_rope(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
397 recognize_rope(s).map(TensorOpSpec::Rope)
398}
399fn r_silu(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
400 recognize_silu(s).map(TensorOpSpec::Silu)
401}
402fn r_sigmoid(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
403 recognize_sigmoid(s).map(TensorOpSpec::Sigmoid)
404}
405fn r_gelu(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
406 recognize_gelu(s).map(TensorOpSpec::Gelu)
407}
408fn r_scale(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
409 recognize_scale(s).map(TensorOpSpec::Scale)
410}
411fn r_softcap(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
412 recognize_softcap(s).map(TensorOpSpec::Softcap)
413}
414fn r_decay(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
415 recognize_decay(s).map(TensorOpSpec::Decay)
416}
417fn r_causal_conv(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
418 recognize_causal_conv(s).map(TensorOpSpec::CausalConv)
419}
420fn r_gated_delta(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
421 recognize_gated_delta(s).map(TensorOpSpec::GatedDelta)
422}
423fn r_l2norm(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
424 recognize_l2norm(s).map(TensorOpSpec::L2Norm)
425}
426fn r_fused_row(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
427 recognize_fused_row(s, i).ok().map(TensorOpSpec::FusedRow)
428}
429fn r_transpose(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
430 recognize_transpose(s).map(TensorOpSpec::Transpose)
431}
432fn r_head_slice(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
433 recognize_head_slice(s).map(TensorOpSpec::HeadSlice)
434}
435fn r_join(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
436 recognize_join(s).map(TensorOpSpec::Join)
437}
438fn r_head_matmul(s: &[Stmt], _: &Interner) -> Option<TensorOpSpec> {
439 recognize_head_matmul(s).map(TensorOpSpec::HeadMatmul)
440}
441fn r_swiglu(s: &[Stmt], i: &Interner) -> Option<TensorOpSpec> {
442 recognize_swiglu(s, i).ok().map(TensorOpSpec::SwiGlu)
443}
444
445pub const RECOGNIZER_ORDER: &[(&str, Recognizer)] = &[
449 ("moe", r_moe),
450 ("lut_attention", r_lut_attention),
451 ("attention", r_attention),
452 ("full_attention", r_full_attention),
453 ("row_gather", r_row_gather),
454 ("rows_gather", r_rows_gather),
455 ("argmax", r_argmax),
456 ("set_row", r_set_row),
457 ("head_matmul", r_head_matmul),
458 ("matmul", r_matmul),
459 ("softcap", r_softcap),
460 ("elementwise", r_elementwise),
461 ("int_requant", r_int_requant),
462 ("int_clamp", r_int_clamp),
463 ("relu", r_relu),
464 ("rope", r_rope),
465 ("silu", r_silu),
466 ("sigmoid", r_sigmoid),
467 ("gelu", r_gelu),
468 ("scale", r_scale),
469 ("decay", r_decay),
470 ("causal_conv", r_causal_conv),
471 ("gated_delta", r_gated_delta),
472 ("l2norm", r_l2norm),
473 ("fused_row", r_fused_row),
474 ("transpose", r_transpose),
475 ("head_slice", r_head_slice),
476 ("join", r_join),
477 ("swiglu", r_swiglu),
478];
479
480pub fn recognize_stages(source: &str) -> Result<RecognizedProgram, SpatialReject> {
483 crate::ui_bridge::with_parsed_tensor_program(source, |parsed, interner, shapes| {
484 let (stmts, _types, _policies) = parsed.map_err(SpatialReject::Parse)?;
485 let mut rp = walk(stmts, interner);
486 rp.shapes = shapes
487 .iter()
488 .filter(|(_, (r, c))| *r > 0 && *c > 0)
489 .map(|(sym, (r, c))| {
490 (
491 interner.resolve(*sym).to_string(),
492 (*r as usize, *c as usize),
493 )
494 })
495 .collect();
496 Ok(rp)
497 })
498}
499
500pub fn walk(stmts: &[Stmt], interner: &Interner) -> RecognizedProgram {
504 let mut rp = RecognizedProgram::default();
505 let mut tensors: std::collections::HashSet<Symbol> = Default::default();
506 let (fills, assigned) = (Fills::of(stmts), assigned(stmts));
507 let (mut declared, mut staged): (std::collections::HashSet<String>, std::collections::HashSet<String>) = Default::default();
509 for (i, s) in stmts.iter().enumerate() {
510 let touched = crate::optimize::mentions::Mentions::of_stmt(s);
511 if touched.is_opaque() {
512 rp.opaque.push(i);
513 }
514 for sym in touched.symbols() {
515 rp.mentions.entry(interner.resolve(sym).to_string()).or_default().insert(i);
516 }
517 let bound = |t: Symbol| {
521 let name = interner.resolve(t);
522 declared.contains(name) || staged.contains(name)
523 };
524 let declaration = tensor_declaration(s)
525 .map(|(var, len)| (var, len, None))
526 .or_else(|| scalar_declaration(s, &assigned).map(|(var, fill)| (var, 1, Some(fill))));
527 if let Some((var, len, scalar)) = declaration {
528 if bound(var) {
529 rp.unlifted_writes
530 .push((i, interner.resolve(var).to_string(), SECOND_BINDING));
531 continue;
532 }
533 let fill = scalar.unwrap_or_else(|| {
534 tensors.insert(var);
535 fills.fill(var)
536 });
537 declared.insert(interner.resolve(var).to_string());
538 rp.quiet.insert(i);
539 rp.declared.push(DeclaredTensor {
540 name: interner.resolve(var).to_string(),
541 len,
542 fill,
543 });
544 continue;
545 }
546 if !is_compound(s) {
547 let touched = |t: Symbol| staged.contains(interner.resolve(t));
548 match unlifted_write(s, &tensors, &bound, &touched) {
549 Some((t, why)) => rp.unlifted_writes.push((i, interner.resolve(t).to_string(), why)),
550 None if matches!(s, Stmt::SetIndex { collection: Expr::Identifier(t), .. } if tensors.contains(t)) => {
551 rp.quiet.insert(i);
552 }
553 None => {}
554 }
555 continue;
556 }
557 let one = std::slice::from_ref(s);
558 match RECOGNIZER_ORDER.iter().find_map(|(_, r)| r(one, interner)) {
559 Some(op) => {
560 let out = op.out();
561 let ins = op.ins();
562 for sym in std::iter::once(out).chain(ins.iter().copied()) {
563 rp.symbols
564 .entry(sym)
565 .or_insert_with(|| interner.resolve(sym).to_string());
566 }
567 staged.extend(std::iter::once(out).chain(ins.iter().copied()).map(|t| interner.resolve(t).to_string()));
568 rp.stages.push(RecognizedStage {
569 out: interner.resolve(out).to_string(),
570 ins: ins
571 .iter()
572 .map(|s| interner.resolve(*s).to_string())
573 .collect(),
574 op,
575 stmt_index: i,
576 });
577 }
578 None => rp.unrecognized.push((i, describe(s, interner))),
579 }
580 }
581 rp
582}
583
584fn tensor_declaration(s: &Stmt) -> Option<(Symbol, usize)> {
587 let Stmt::Splice { body } = s else {
588 return None;
589 };
590 let [Stmt::Let {
591 var, value: new, ..
592 }, Stmt::Let {
593 var: ctr,
594 value: start,
595 ..
596 }, Stmt::While {
597 cond, body: fill, ..
598 }] = body
599 else {
600 return None;
601 };
602 if !matches!(new, Expr::New { .. }) || !matches!(start, Expr::Literal(Literal::Number(0))) {
603 return None;
604 }
605 let Expr::BinaryOp { left, right, .. } = cond else {
606 return None;
607 };
608 let (Expr::Identifier(iv), Expr::Literal(Literal::Number(len))) = (*left, *right) else {
609 return None;
610 };
611 let [Stmt::Push { value, collection }, Stmt::Set { .. }] = fill else {
612 return None;
613 };
614 let zero = matches!(value, Expr::Literal(Literal::Number(0)))
615 || matches!(value, Expr::Literal(Literal::Float(f)) if *f == 0.0);
616 (iv == ctr && zero && matches!(collection, Expr::Identifier(c) if c == var) && *len >= 0)
617 .then_some((*var, *len as usize))
618}
619
620fn scalar_declaration(s: &Stmt, assigned: &std::collections::HashSet<Symbol>) -> Option<(Symbol, Fill)> {
624 let Stmt::Let {
625 var,
626 value: Expr::Literal(Literal::Number(n)),
627 ..
628 } = s
629 else {
630 return None;
631 };
632 Some((
633 *var,
634 if assigned.contains(var) {
635 Fill::Unknown
636 } else {
637 Fill::Const(*n)
638 },
639 ))
640}
641
642fn assigned(stmts: &[Stmt]) -> std::collections::HashSet<Symbol> {
644 fn walk(stmts: &[Stmt], into: &mut std::collections::HashSet<Symbol>) {
645 for s in stmts {
646 match s {
647 Stmt::Set { target, .. } => {
648 into.insert(*target);
649 }
650 Stmt::Splice { body } | Stmt::While { body, .. } | Stmt::Repeat { body, .. } => walk(body, into),
651 Stmt::If {
652 then_block,
653 else_block,
654 ..
655 } => {
656 walk(then_block, into);
657 if let Some(eb) = else_block {
658 walk(eb, into);
659 }
660 }
661 _ => {}
662 }
663 }
664 }
665 let mut into = std::collections::HashSet::new();
666 walk(stmts, &mut into);
667 into
668}
669
670const SECOND_BINDING: &str = "a second binding of a name already bound — every section reads the value it was first bound to";
672
673fn unlifted_write(
681 s: &Stmt,
682 tensors: &std::collections::HashSet<Symbol>,
683 bound: &dyn Fn(Symbol) -> bool,
684 touched: &dyn Fn(Symbol) -> bool,
685) -> Option<(Symbol, &'static str)> {
686 let ident = |e: &Expr| match e {
687 Expr::Identifier(t) => Some(*t),
688 _ => None,
689 };
690 match s {
691 Stmt::SetIndex {
692 collection,
693 index,
694 value,
695 } => {
696 let t = ident(collection).filter(|t| tensors.contains(t))?;
697 let literal = matches!(index, Expr::Literal(Literal::Number(_)))
698 && matches!(value, Expr::Literal(Literal::Number(_) | Literal::Float(_)));
699 if !literal {
700 Some((
701 t,
702 "a write whose position or value is not a literal — data no section computes",
703 ))
704 } else if touched(t) {
705 Some((t, "a literal write after an op has used the tensor — folding it into the fill would move it before that op"))
706 } else {
707 None
708 }
709 }
710 Stmt::Push { collection, .. } => ident(collection)
711 .filter(|t| tensors.contains(t))
712 .map(|t| (t, "a push that grows a declared tensor")),
713 Stmt::Set { target, .. } | Stmt::Let { var: target, .. } if tensors.contains(target) => {
714 Some((*target, "a rebinding of a declared tensor"))
715 }
716 Stmt::Let { var, .. } if bound(*var) => Some((*var, SECOND_BINDING)),
717 Stmt::Set { target, .. } if touched(*target) => {
718 Some((*target, "a write to a value an op has already read"))
719 }
720 _ => None,
721 }
722}
723
724fn is_compound(s: &Stmt) -> bool {
726 matches!(
727 s,
728 Stmt::Splice { .. } | Stmt::While { .. } | Stmt::Repeat { .. } | Stmt::If { .. }
729 )
730}
731
732fn describe(s: &Stmt, interner: &Interner) -> String {
735 match s {
736 Stmt::Splice { body } => body
737 .iter()
738 .find_map(|st| match st {
739 Stmt::Let { var, .. } => Some(interner.resolve(*var).to_string()),
740 _ => None,
741 })
742 .unwrap_or_else(|| "Splice".to_string()),
743 Stmt::While { cond, .. } => match cond {
744 Expr::BinaryOp {
745 left: Expr::Identifier(iv),
746 ..
747 } => format!("While {}", interner.resolve(*iv)),
748 _ => "While".to_string(),
749 },
750 Stmt::Repeat { .. } => "Repeat".to_string(),
751 Stmt::If { .. } => "If".to_string(),
752 _ => "statement".to_string(),
753 }
754}
755
756pub fn links(stages: &[RecognizedStage]) -> Vec<Link> {
758 let mut readers: BTreeMap<&str, Vec<usize>> = BTreeMap::new();
759 for (j, s) in stages.iter().enumerate() {
760 for name in &s.ins {
761 let r = readers.entry(name.as_str()).or_default();
762 if r.last() != Some(&j) {
763 r.push(j);
764 }
765 }
766 }
767 stages
768 .iter()
769 .enumerate()
770 .map(|(i, s)| Link {
771 producer: i,
772 consumers: readers
773 .get(s.out.as_str())
774 .map(|r| r.iter().copied().filter(|&j| j > i).collect())
775 .unwrap_or_default(),
776 tensor: s.out.clone(),
777 })
778 .collect()
779}
780
781pub fn carried_state(rp: &RecognizedProgram) -> Vec<String> {
788 let assembled = |table: &str| -> bool {
789 let mut rows: Option<usize> = None;
790 let mut covered: BTreeSet<usize> = BTreeSet::new();
791 for s in &rp.stages {
792 let writes = matches!(&s.op, TensorOpSpec::SetRow(_)) && s.ins[0] == table;
793 let reads = s
794 .ins
795 .iter()
796 .enumerate()
797 .any(|(k, n)| n == table && !(writes && k == 0));
798 if reads && rows.is_none_or(|r| covered.len() < r) {
799 return false;
800 }
801 if let (true, TensorOpSpec::SetRow(r)) = (writes, &s.op) {
802 let RowIndex::Literal(first) = r.index else {
803 return false;
804 };
805 rows = Some(r.rows);
806 covered.extend(
807 (first as usize).saturating_sub(1)
808 ..(first as usize).saturating_sub(1) + r.count,
809 );
810 }
811 if let Some(n) = s.op.assembled_rows().filter(|_| s.out == table) {
812 rows = Some(n);
813 covered.extend(0..n);
814 }
815 }
816 rows.is_some_and(|r| (0..r).all(|y| covered.contains(&y)))
817 };
818 state_tensors(&rp.stages)
819 .into_iter()
820 .filter(|t| !assembled(t))
821 .collect()
822}
823
824pub fn state_tensors(stages: &[RecognizedStage]) -> Vec<String> {
827 let mut seen: BTreeSet<&str> = BTreeSet::new();
828 let mut out: Vec<String> = Vec::new();
829 for s in stages {
830 for pos in s.op.state_inputs() {
831 let name = &s.ins[pos];
832 if seen.insert(name.as_str()) {
833 out.push(name.clone());
834 }
835 }
836 if s.op.assembled_rows().is_some() && seen.insert(s.out.as_str()) {
837 out.push(s.out.clone());
838 }
839 }
840 out
841}