1use crate::intern::{Interner, Symbol};
11use logicaffeine_language::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt, TypeExpr};
12use std::collections::HashMap;
13
14pub mod qwen;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct MatmulSpec {
19 pub m: usize,
20 pub n: usize,
21 pub k: usize,
22 pub a: Symbol,
23 pub b: Symbol,
24 pub c: Symbol,
25}
26
27impl MatmulSpec {
28 pub fn mt(&self) -> usize {
30 self.m.div_ceil(32)
31 }
32 pub fn nt(&self) -> usize {
34 self.n.div_ceil(32)
35 }
36 pub fn kt(&self) -> usize {
38 self.k.div_ceil(32)
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct HeadMatmulSpec {
47 pub x: Symbol,
48 pub w: Symbol,
49 pub out: Symbol,
50 pub m: usize,
51 pub heads: usize,
52 pub a: usize,
53 pub b: usize,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum MatmulReject {
59 NoLoopNest,
61 NotTripleNested,
63 NoAccumulatingStore,
65 NonAffineIndex,
67 NotGemmLayout,
69}
70
71#[derive(Clone, Default, Debug, PartialEq, Eq)]
73struct Affine {
74 coeff: HashMap<Symbol, i64>,
75 cst: i64,
76}
77
78impl Affine {
79 fn constant(c: i64) -> Self {
80 Affine {
81 coeff: HashMap::new(),
82 cst: c,
83 }
84 }
85 fn var(s: Symbol) -> Self {
86 let mut coeff = HashMap::new();
87 coeff.insert(s, 1);
88 Affine { coeff, cst: 0 }
89 }
90 fn add(&self, o: &Affine) -> Affine {
91 let mut coeff = self.coeff.clone();
92 for (k, v) in &o.coeff {
93 *coeff.entry(*k).or_default() += v;
94 }
95 coeff.retain(|_, v| *v != 0);
96 Affine {
97 coeff,
98 cst: self.cst + o.cst,
99 }
100 }
101 fn sub(&self, o: &Affine) -> Affine {
102 let mut coeff = self.coeff.clone();
103 for (k, v) in &o.coeff {
104 *coeff.entry(*k).or_default() -= v;
105 }
106 coeff.retain(|_, v| *v != 0);
107 Affine {
108 coeff,
109 cst: self.cst - o.cst,
110 }
111 }
112 fn scale(&self, f: i64) -> Affine {
113 Affine {
114 coeff: self.coeff.iter().map(|(k, v)| (*k, v * f)).collect(),
115 cst: self.cst * f,
116 }
117 }
118 fn is_const(&self) -> bool {
119 self.coeff.is_empty()
120 }
121 fn coeff_of(&self, s: Symbol) -> i64 {
122 *self.coeff.get(&s).unwrap_or(&0)
123 }
124}
125
126fn eval_affine(
130 e: &Expr,
131 consts: &HashMap<Symbol, i64>,
132 lets: &HashMap<Symbol, Affine>,
133) -> Option<Affine> {
134 match e {
135 Expr::Literal(Literal::Number(n)) => Some(Affine::constant(*n)),
136 Expr::Identifier(s) => {
137 if let Some(c) = consts.get(s) {
138 Some(Affine::constant(*c))
139 } else if let Some(a) = lets.get(s) {
140 Some(a.clone())
141 } else {
142 Some(Affine::var(*s))
143 }
144 }
145 Expr::BinaryOp { op, left, right } => {
146 let l = eval_affine(left, consts, lets)?;
147 let r = eval_affine(right, consts, lets)?;
148 match op {
149 BinaryOpKind::Add => Some(l.add(&r)),
150 BinaryOpKind::Subtract => Some(l.sub(&r)),
151 BinaryOpKind::Multiply => {
152 if l.is_const() {
153 Some(r.scale(l.cst))
154 } else if r.is_const() {
155 Some(l.scale(r.cst))
156 } else {
157 None }
159 }
160 _ => None,
161 }
162 }
163 _ => None,
164 }
165}
166
167fn first_while<'a>(stmts: &'a [Stmt<'a>]) -> Option<(&'a Expr<'a>, &'a [Stmt<'a>])> {
169 stmts.iter().find_map(|s| match s {
170 Stmt::While { cond, body, .. } => Some((*cond, *body)),
171 _ => None,
172 })
173}
174
175fn loop_iv_bound(cond: &Expr, consts: &HashMap<Symbol, i64>) -> Option<(Symbol, i64)> {
177 let empty = HashMap::new();
178 if let Expr::BinaryOp { op, left, right } = cond {
179 if let Expr::Identifier(iv) = left {
180 let b = eval_affine(right, consts, &empty)?;
181 if b.is_const() {
182 return match op {
183 BinaryOpKind::Lt => Some((*iv, b.cst)),
184 BinaryOpKind::LtEq => Some((*iv, b.cst + 1)),
185 _ => None,
186 };
187 }
188 }
189 }
190 None
191}
192
193fn as_ident(e: &Expr) -> Option<Symbol> {
194 if let Expr::Identifier(s) = e {
195 Some(*s)
196 } else {
197 None
198 }
199}
200
201fn as_index<'a>(e: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
202 if let Expr::Index { collection, index } = e {
203 Some((collection, index))
204 } else {
205 None
206 }
207}
208
209fn as_binop<'a>(e: &'a Expr<'a>, want: BinaryOpKind) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
210 if let Expr::BinaryOp { op, left, right } = e {
211 if *op == want {
212 return Some((left, right));
213 }
214 }
215 None
216}
217
218fn collect_consts_rec(stmts: &[Stmt], out: &mut HashMap<Symbol, i64>) {
221 for s in stmts {
222 match s {
223 Stmt::Let {
226 var,
227 value,
228 mutable: false,
229 ..
230 } => {
231 if let Expr::Literal(Literal::Number(n)) = value {
232 out.insert(*var, *n);
233 }
234 }
235 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
236 collect_consts_rec(body, out)
237 }
238 Stmt::If {
239 then_block,
240 else_block,
241 ..
242 } => {
243 collect_consts_rec(then_block, out);
244 if let Some(eb) = else_block {
245 collect_consts_rec(eb, out);
246 }
247 }
248 _ => {}
249 }
250 }
251}
252
253fn reject_rank(r: &MatmulReject) -> u8 {
254 match r {
255 MatmulReject::NoLoopNest => 1,
256 MatmulReject::NotTripleNested => 2,
257 MatmulReject::NoAccumulatingStore => 3,
258 MatmulReject::NonAffineIndex => 4,
259 MatmulReject::NotGemmLayout => 5,
260 }
261}
262
263pub fn recognize_matmul(stmts: &[Stmt], _interner: &Interner) -> Result<MatmulSpec, MatmulReject> {
272 let mut consts: HashMap<Symbol, i64> = HashMap::new();
273 collect_consts_rec(stmts, &mut consts);
274 search_matmul(stmts, &consts)
275}
276
277pub fn recognize_head_matmul(stmts: &[Stmt]) -> Option<HeadMatmulSpec> {
281 let mut consts: HashMap<Symbol, i64> = HashMap::new();
282 collect_consts_rec(stmts, &mut consts);
283 search_head_matmul(stmts, &consts)
284}
285
286fn search_head_matmul(stmts: &[Stmt], consts: &HashMap<Symbol, i64>) -> Option<HeadMatmulSpec> {
287 for s in stmts {
288 let found = match s {
289 Stmt::While { cond, body, .. } => {
290 head_matmul_at(cond, body, consts).or_else(|| search_head_matmul(body, consts))
291 }
292 Stmt::Splice { body } => search_head_matmul(body, consts),
293 Stmt::If { then_block, else_block, .. } => search_head_matmul(then_block, consts)
294 .or_else(|| else_block.and_then(|eb| search_head_matmul(eb, consts))),
295 _ => None,
296 };
297 if found.is_some() {
298 return found;
299 }
300 }
301 None
302}
303
304fn head_matmul_at(cond_t: &Expr, body_t: &[Stmt], consts: &HashMap<Symbol, i64>) -> Option<HeadMatmulSpec> {
306 let (t, m) = loop_iv_bound(cond_t, consts)?;
307 let (cond_h, body_h) = first_while(body_t)?;
308 let (h, heads) = loop_iv_bound(cond_h, consts)?;
309 let (cond_e, body_e) = first_while(body_h)?;
310 let (e, a) = loop_iv_bound(cond_e, consts)?;
311 let (cond_r, body_r) = first_while(body_e)?;
312 let (r, b) = loop_iv_bound(cond_r, consts)?;
313 let none = HashMap::new();
314 let (out, idx, value) = body_r.iter().find_map(|s| match s {
315 Stmt::SetIndex { collection, index, value } => Some((as_ident(collection)?, *index, *value)),
316 _ => None,
317 })?;
318 let y = eval_affine(idx, consts, &none)?;
319 let (acc, prod) = as_binop(value, BinaryOpKind::Add)?;
320 let (acc_c, acc_i) = as_index(acc)?;
321 if as_ident(acc_c)? != out || eval_affine(acc_i, consts, &none)? != y {
322 return None;
323 }
324 let (xr, wr) = as_binop(prod, BinaryOpKind::Multiply)?;
325 let (xc, xi) = as_index(xr)?;
326 let (wc, wi) = as_index(wr)?;
327 let (x, w) = (as_ident(xc)?, as_ident(wc)?);
328 let (xi, wi) = (eval_affine(xi, consts, &none)?, eval_affine(wi, consts, &none)?);
329 let shaped = |f: &Affine, coeffs: [i64; 4]| {
330 [t, h, e, r].iter().zip(coeffs).all(|(v, c)| f.coeff_of(*v) == c) && f.coeff.len() == coeffs.iter().filter(|c| **c != 0).count() && f.cst == 1
331 };
332 let ok = shaped(&y, [heads * b, b, 0, 1]) && shaped(&xi, [heads * a, a, 1, 0]) && shaped(&wi, [0, a * b, b, 1]);
333 (ok && m >= 1 && heads >= 1 && a >= 1 && b >= 1).then(|| HeadMatmulSpec {
334 x,
335 w,
336 out,
337 m: m as usize,
338 heads: heads as usize,
339 a: a as usize,
340 b: b as usize,
341 })
342}
343
344#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
347pub enum Fill {
348 Const(i64),
350 Pattern { modulus: i64, shift: i64 },
352 Overrides {
354 base: Box<Fill>,
355 sets: Vec<(usize, i64)>,
356 },
357 ConstF(u64),
359 FloatOverrides { base: u64, sets: Vec<(usize, u64)> },
361 RealPattern { modulus: i64, shift: i64, divisor: u64 },
363 Unknown,
365}
366
367impl Fill {
368 pub fn float_sets(&self) -> Option<(f64, Vec<(usize, f64)>)> {
370 match self {
371 Fill::ConstF(b) => Some((f64::from_bits(*b), Vec::new())),
372 Fill::FloatOverrides { base, sets } => Some((
373 f64::from_bits(*base),
374 sets.iter().map(|(i, b)| (*i, f64::from_bits(*b))).collect(),
375 )),
376 _ => None,
377 }
378 }
379
380 pub fn element(&self, p: i64) -> Option<Scalar> {
383 match self {
384 Fill::Const(c) => Some(Scalar::Int(*c)),
385 Fill::Pattern { modulus, shift } => Some(Scalar::Int(p % modulus + shift)),
386 Fill::ConstF(b) => Some(Scalar::Real(f64::from_bits(*b))),
387 Fill::RealPattern { modulus, shift, divisor } => {
388 Some(Scalar::Real((p % modulus + shift) as f64 / f64::from_bits(*divisor)))
389 }
390 Fill::Overrides { .. } | Fill::FloatOverrides { .. } | Fill::Unknown => None,
391 }
392 }
393}
394
395pub fn recognize_fill(stmts: &[Stmt], tensor: Symbol) -> Fill {
397 Fills::of(stmts).fill(tensor)
398}
399
400pub struct Fills<'s, 'a> {
404 first: std::collections::HashMap<Symbol, Fill>,
405 writes: std::collections::HashMap<Symbol, Vec<&'s Stmt<'a>>>,
406}
407
408impl<'s, 'a> Fills<'s, 'a> {
409 pub fn of(stmts: &'s [Stmt<'a>]) -> Self {
410 fn scan(stmts: &[Stmt], first: &mut std::collections::HashMap<Symbol, Fill>) {
411 for s in stmts {
412 match s {
413 Stmt::While { cond, body, .. } => {
414 let iv = as_binop_any(cond).and_then(|(l, _)| as_ident(l));
415 for b in body.iter() {
416 if let Stmt::Push { value, collection } = b {
417 if let Some(t) = as_ident(collection) {
418 first.entry(t).or_insert_with(|| fill_of(value, iv));
419 }
420 }
421 }
422 scan(body, first);
423 }
424 Stmt::If { then_block, else_block, .. } => {
425 scan(then_block, first);
426 if let Some(eb) = else_block {
427 scan(eb, first);
428 }
429 }
430 Stmt::Splice { body } => scan(body, first),
431 _ => {}
432 }
433 }
434 }
435 let mut first = std::collections::HashMap::new();
436 scan(stmts, &mut first);
437 let mut writes: std::collections::HashMap<Symbol, Vec<&Stmt>> = std::collections::HashMap::new();
438 for s in stmts {
439 if let Stmt::SetIndex { collection, .. } = s {
440 if let Some(t) = as_ident(collection) {
441 writes.entry(t).or_default().push(s);
442 }
443 }
444 }
445 Fills { first, writes }
446 }
447
448 pub fn fill(&self, tensor: Symbol) -> Fill {
450 let base = self.first.get(&tensor).cloned().unwrap_or(Fill::Unknown);
451 if base == Fill::Unknown {
452 return base;
453 }
454 let writes: &[&Stmt] = self.writes.get(&tensor).map_or(&[], Vec::as_slice);
455 if !writes.is_empty() && matches!(base, Fill::RealPattern { .. }) {
456 return Fill::Unknown;
457 }
458 let mut sets = Vec::new();
459 let mut float_sets: Vec<(usize, u64)> = Vec::new();
460 for s in writes {
461 if let Stmt::SetIndex {
462 index: Expr::Literal(Literal::Number(i)),
463 value,
464 ..
465 } = s
466 {
467 if *i >= 1 {
468 match value {
469 Expr::Literal(Literal::Number(v)) => sets.push((*i as usize - 1, *v)),
470 Expr::Literal(Literal::Float(v)) => float_sets.push((*i as usize - 1, v.to_bits())),
471 _ => {}
472 }
473 }
474 }
475 }
476 let float_base = match &base {
477 Fill::ConstF(b) => Some(*b),
478 Fill::Const(c) if !float_sets.is_empty() => Some((*c as f64).to_bits()),
479 _ => None,
480 };
481 if let Some(fb) = float_base {
482 let mut all: Vec<(usize, u64)> = sets
483 .iter()
484 .map(|(i, v)| (*i, (*v as f64).to_bits()))
485 .collect();
486 all.extend(float_sets);
487 return if all.is_empty() {
488 Fill::ConstF(fb)
489 } else {
490 Fill::FloatOverrides {
491 base: fb,
492 sets: all,
493 }
494 };
495 }
496 if sets.is_empty() {
497 base
498 } else {
499 Fill::Overrides {
500 base: Box::new(base),
501 sets,
502 }
503 }
504 }
505}
506
507fn as_binop_any<'a>(e: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
508 if let Expr::BinaryOp { left, right, .. } = e {
509 Some((left, right))
510 } else {
511 None
512 }
513}
514
515fn fill_of(value: &Expr, iv: Option<Symbol>) -> Fill {
518 match value {
519 Expr::BinaryOp {
520 op: BinaryOpKind::Divide,
521 left,
522 right: Expr::Literal(Literal::Float(d)),
523 } if d.is_finite() && *d != 0.0 => match fill_of(left, iv) {
524 Fill::Pattern { modulus, shift } => Fill::RealPattern { modulus, shift, divisor: d.to_bits() },
525 _ => Fill::Unknown,
526 },
527 Expr::Literal(Literal::Number(n)) => Fill::Const(*n),
528 Expr::Literal(Literal::Float(f)) => Fill::ConstF(f.to_bits()),
529 Expr::BinaryOp {
530 op: BinaryOpKind::Modulo,
531 left,
532 right,
533 } => match (as_ident(left), right) {
534 (Some(v), Expr::Literal(Literal::Number(m))) if Some(v) == iv && *m > 0 => {
535 Fill::Pattern {
536 modulus: *m,
537 shift: 0,
538 }
539 }
540 _ => Fill::Unknown,
541 },
542 Expr::BinaryOp { op, left, right }
543 if matches!(op, BinaryOpKind::Add | BinaryOpKind::Subtract) =>
544 {
545 match (fill_of(left, iv), right) {
546 (Fill::Pattern { modulus, shift }, Expr::Literal(Literal::Number(c))) => {
547 let c = if *op == BinaryOpKind::Add { *c } else { -*c };
548 Fill::Pattern {
549 modulus,
550 shift: shift + c,
551 }
552 }
553 _ => Fill::Unknown,
554 }
555 }
556 _ => Fill::Unknown,
557 }
558}
559
560#[derive(
562 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
563)]
564pub enum ElementwiseOp {
565 Add,
566 Mul,
567}
568
569impl ElementwiseOp {
570 pub fn c_op(self) -> &'static str {
572 match self {
573 ElementwiseOp::Add => "+",
574 ElementwiseOp::Mul => "*",
575 }
576 }
577}
578
579#[derive(Debug, Clone, Copy)]
582pub struct ElementwiseSpec {
583 pub a: Symbol,
584 pub b: Symbol,
585 pub c: Symbol,
586 pub op: ElementwiseOp,
587 pub n: usize,
588 pub row: Option<usize>,
591}
592
593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595pub enum ElementwiseReject {
596 NotElementwise,
597}
598
599pub fn recognize_elementwise(
602 stmts: &[Stmt],
603 _interner: &Interner,
604) -> Result<ElementwiseSpec, ElementwiseReject> {
605 let mut consts: HashMap<Symbol, i64> = HashMap::new();
606 collect_consts_rec(stmts, &mut consts);
607 search_elementwise(stmts, &consts, None).ok_or(ElementwiseReject::NotElementwise)
608}
609
610fn search_elementwise(
611 stmts: &[Stmt],
612 consts: &HashMap<Symbol, i64>,
613 binder: Option<Symbol>,
614) -> Option<ElementwiseSpec> {
615 for s in stmts {
616 match s {
617 Stmt::While { cond, body, .. } => {
618 if let Some(spec) = try_elementwise_at(cond, body, consts, binder) {
619 return Some(spec);
620 }
621 if let Some(spec) = search_elementwise(body, consts, binder) {
622 return Some(spec);
623 }
624 }
625 Stmt::If {
626 then_block,
627 else_block,
628 ..
629 } => {
630 if let Some(spec) = search_elementwise(then_block, consts, binder) {
631 return Some(spec);
632 }
633 if let Some(eb) = else_block {
634 if let Some(spec) = search_elementwise(eb, consts, binder) {
635 return Some(spec);
636 }
637 }
638 }
639 Stmt::Splice { body } => {
640 if let Some(spec) = search_elementwise(body, consts, splice_binder(body).or(binder))
641 {
642 return Some(spec);
643 }
644 }
645 _ => {}
646 }
647 }
648 None
649}
650
651fn ew_loop_bound(cond: &Expr, consts: &HashMap<Symbol, i64>) -> Option<(Symbol, i64)> {
653 let empty = HashMap::new();
654 if let Expr::BinaryOp { op, left, right } = cond {
655 if let Expr::Identifier(iv) = left {
656 let b = eval_affine(right, consts, &empty)?;
657 if b.is_const() {
658 return match op {
659 BinaryOpKind::LtEq => Some((*iv, b.cst)), BinaryOpKind::Lt => Some((*iv, b.cst - 1)), _ => None,
662 };
663 }
664 }
665 }
666 None
667}
668
669fn try_elementwise_at(
670 cond: &Expr,
671 body: &[Stmt],
672 consts: &HashMap<Symbol, i64>,
673 binder: Option<Symbol>,
674) -> Option<ElementwiseSpec> {
675 let (iv, n) = ew_loop_bound(cond, consts)?;
676 if n <= 0 {
677 return None;
678 }
679 for st in body {
680 let Stmt::Push { value, collection } = st else {
681 continue;
682 };
683 let c = as_ident(collection)?;
684 if !pushes_into_binder(c, binder) {
685 continue;
686 }
687 let Expr::BinaryOp { op, left, right } = value else {
688 continue;
689 };
690 let ewop = match op {
691 BinaryOpKind::Add => ElementwiseOp::Add,
692 BinaryOpKind::Multiply => ElementwiseOp::Mul,
693 _ => continue,
694 };
695 let (a_coll, a_idx) = as_index(left)?;
696 let (b_coll, b_idx) = as_index(right)?;
697 let row = match as_ident(b_idx) {
699 Some(i) if i == iv => Some(None),
700 _ => broadcast_width(b_idx, iv).map(Some),
701 };
702 if let (Some(row), true) = (row, as_ident(a_idx) == Some(iv)) {
703 return Some(ElementwiseSpec {
704 a: as_ident(a_coll)?,
705 b: as_ident(b_coll)?,
706 c,
707 op: ewop,
708 n: n as usize,
709 row,
710 });
711 }
712 }
713 None
714}
715
716fn broadcast_width(e: &Expr, iv: Symbol) -> Option<usize> {
718 let (m, one) = as_binop(e, BinaryOpKind::Add)?;
719 let (shifted, w) = as_binop(m, BinaryOpKind::Modulo)?;
720 let (i, one2) = as_binop(shifted, BinaryOpKind::Subtract)?;
721 let lit = |e: &Expr| match e {
722 Expr::Literal(Literal::Number(n)) => Some(*n),
723 _ => None,
724 };
725 (lit(one)? == 1 && lit(one2)? == 1 && as_ident(i)? == iv).then_some(())?;
726 lit(w).filter(|w| *w > 0).map(|w| w as usize)
727}
728
729#[derive(
731 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
732)]
733pub enum ReductionKind {
734 Rmsnorm,
735 Softmax,
736}
737
738impl ReductionKind {
739 pub fn name(self) -> &'static str {
740 match self {
741 ReductionKind::Rmsnorm => "rmsnorm",
742 ReductionKind::Softmax => "softmax",
743 }
744 }
745}
746
747#[derive(Debug, Clone, Copy)]
750pub struct ReductionSpec {
751 pub kind: ReductionKind,
752 pub x: Symbol,
753 pub y: Symbol,
754 pub n: usize,
755 pub denom: Option<Symbol>,
756}
757
758#[derive(
761 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
762)]
763pub enum RowReduceKind {
764 RmsNorm,
766 Softmax,
768 LayerNorm,
770}
771
772impl RowReduceKind {
773 pub fn name(self) -> &'static str {
774 match self {
775 RowReduceKind::RmsNorm => "rmsnorm",
776 RowReduceKind::Softmax => "softmax",
777 RowReduceKind::LayerNorm => "layernorm",
778 }
779 }
780}
781
782#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
784pub struct Epsilon(u64);
785
786impl Epsilon {
787 pub const DEFAULT: Epsilon = Epsilon(1e-6f64.to_bits());
789
790 pub fn new(v: f64) -> Epsilon {
791 Epsilon(v.to_bits())
792 }
793
794 pub fn get(self) -> f64 {
795 f64::from_bits(self.0)
796 }
797}
798
799#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
801pub struct Factor(u64);
802
803impl Factor {
804 pub fn new(v: f64) -> Factor {
805 Factor(v.to_bits())
806 }
807
808 pub fn get(self) -> f64 {
809 f64::from_bits(self.0)
810 }
811}
812
813pub fn epsilon_of(stmts: &[Stmt], denom: Symbol) -> Option<Epsilon> {
815 for s in stmts {
816 let found = match s {
817 Stmt::Let { var, value, .. } if *var == denom => sqrt_epsilon(value),
818 Stmt::Set { target, value } if *target == denom => sqrt_epsilon(value),
819 Stmt::While { body, .. } | Stmt::Splice { body } => epsilon_of(body, denom),
820 Stmt::If { then_block, else_block, .. } => {
821 epsilon_of(then_block, denom).or_else(|| else_block.and_then(|e| epsilon_of(e, denom)))
822 }
823 _ => None,
824 };
825 if found.is_some() {
826 return found;
827 }
828 }
829 None
830}
831
832fn sqrt_epsilon(value: &Expr) -> Option<Epsilon> {
834 let Expr::Call { args, .. } = value else { return None };
835 let [arg] = args.as_slice() else { return None };
836 let (_, eps) = as_binop(arg, BinaryOpKind::Add)?;
837 match eps {
838 Expr::Literal(Literal::Float(f)) if *f > 0.0 => Some(Epsilon::new(*f)),
839 _ => None,
840 }
841}
842
843#[derive(Debug, Clone, PartialEq, Eq)]
849pub struct FusedRowSpec {
850 pub rows: usize,
851 pub cols: usize,
852 pub reduce: RowReduceKind,
853 pub x: Symbol,
854 pub y: Symbol,
855 pub weight: Option<Symbol>,
857 pub weight_rows: usize,
860 pub residual: Option<Symbol>,
863 pub bias: Option<Symbol>,
866 pub eps: Epsilon,
868}
869
870pub fn recognize_fused_row(
875 stmts: &[Stmt],
876 interner: &Interner,
877) -> Result<FusedRowSpec, ReductionReject> {
878 let mut consts: HashMap<Symbol, i64> = HashMap::new();
879 collect_consts_rec(stmts, &mut consts);
880 if let Some((mut spec, denom)) = search_weighted_rmsnorm(stmts, &consts, &mut Vec::new(), None) {
881 if let Some(denom) = denom {
882 spec.eps = epsilon_of(stmts, denom).ok_or(ReductionReject::NotReduction)?;
883 }
884 return Ok(spec);
885 }
886 let r = recognize_reduction(stmts, interner)?;
887 let eps = match r.kind {
888 ReductionKind::Rmsnorm => r.denom.and_then(|d| epsilon_of(stmts, d)).ok_or(ReductionReject::NotReduction)?,
889 ReductionKind::Softmax => Epsilon::DEFAULT,
890 };
891 Ok(FusedRowSpec {
892 rows: 1,
893 cols: r.n,
894 reduce: match r.kind {
895 ReductionKind::Rmsnorm => RowReduceKind::RmsNorm,
896 ReductionKind::Softmax => RowReduceKind::Softmax,
897 },
898 x: r.x,
899 y: r.y,
900 weight: None,
901 weight_rows: 1,
902 residual: None,
903 bias: None,
904 eps,
905 })
906}
907
908fn match_layernorm_push(value: &Expr) -> Option<(Symbol, Symbol, Symbol, Symbol)> {
911 let (scaled, b_index) = as_binop(value, BinaryOpKind::Add)?;
912 let (b_coll, _) = as_index(b_index)?;
913 let (div, w_index) = as_binop(scaled, BinaryOpKind::Multiply)?;
914 let (w_coll, _) = as_index(w_index)?;
915 let (sub, denom) = as_binop(div, BinaryOpKind::Divide)?;
916 let denom = as_ident(denom)?;
918 let (x_read, mean) = as_binop(sub, BinaryOpKind::Subtract)?;
919 as_ident(mean)?;
920 let (x_coll, _) = as_index(x_read)?;
921 Some((as_ident(x_coll)?, as_ident(w_coll)?, as_ident(b_coll)?, denom))
922}
923
924fn match_weighted_push(value: &Expr) -> Option<(Symbol, Option<Symbol>, Symbol, Symbol)> {
928 let (div, w_index) = as_binop(value, BinaryOpKind::Multiply)?;
929 let (reduced, denom) = as_binop(div, BinaryOpKind::Divide)?;
930 let denom = as_ident(denom)?;
933 let (w_coll, _widx) = as_index(w_index)?;
934 let weight = as_ident(w_coll)?;
935 if let Some((a, b)) = as_binop(reduced, BinaryOpKind::Add) {
937 let (x_coll, _) = as_index(a)?;
938 let (r_coll, _) = as_index(b)?;
939 return Some((as_ident(x_coll)?, Some(as_ident(r_coll)?), weight, denom));
940 }
941 let (x_coll, _xidx) = as_index(reduced)?;
943 Some((as_ident(x_coll)?, None, weight, denom))
944}
945
946fn search_weighted_rmsnorm(
950 stmts: &[Stmt],
951 consts: &HashMap<Symbol, i64>,
952 bounds: &mut Vec<i64>,
953 binder: Option<Symbol>,
954) -> Option<(FusedRowSpec, Option<Symbol>)> {
955 for s in stmts {
956 match s {
957 Stmt::While { cond, body, .. } => {
958 let pushed = ew_loop_bound(cond, consts)
959 .map(|(_, n)| bounds.push(n))
960 .is_some();
961 if let Some(spec) = search_weighted_rmsnorm(body, consts, bounds, binder) {
962 return Some(spec);
963 }
964 if pushed {
965 bounds.pop();
966 }
967 }
968 Stmt::If {
969 then_block,
970 else_block,
971 ..
972 } => {
973 if let Some(spec) = search_weighted_rmsnorm(then_block, consts, bounds, binder) {
974 return Some(spec);
975 }
976 if let Some(eb) = else_block {
977 if let Some(spec) = search_weighted_rmsnorm(eb, consts, bounds, binder) {
978 return Some(spec);
979 }
980 }
981 }
982 Stmt::Splice { body } => {
983 if let Some(spec) =
984 search_weighted_rmsnorm(body, consts, bounds, splice_binder(body).or(binder))
985 {
986 return Some(spec);
987 }
988 }
989 Stmt::Push { value, collection } => {
990 let y = as_ident(collection)?;
991 if !pushes_into_binder(y, binder) {
992 continue;
993 }
994 let cols = *bounds.last()?;
995 let rows = if bounds.len() >= 2 {
996 bounds[bounds.len() - 2]
997 } else {
998 1
999 };
1000 if cols == 0 || rows == 0 {
1001 continue;
1002 }
1003 if let Some((x, weight, bias, denom)) = match_layernorm_push(value) {
1005 let spec = FusedRowSpec {
1006 rows: rows as usize,
1007 cols: cols as usize,
1008 reduce: RowReduceKind::LayerNorm,
1009 x,
1010 y,
1011 weight: Some(weight),
1012 weight_rows: 1,
1013 residual: None,
1014 bias: Some(bias),
1015 eps: Epsilon::DEFAULT,
1016 };
1017 return Some((spec, Some(denom)));
1018 }
1019 if let Some(x) = match_softmax_push(value) {
1020 let spec = FusedRowSpec {
1021 rows: rows as usize,
1022 cols: cols as usize,
1023 reduce: RowReduceKind::Softmax,
1024 x,
1025 y,
1026 weight: None,
1027 weight_rows: 1,
1028 residual: None,
1029 bias: None,
1030 eps: Epsilon::DEFAULT,
1031 };
1032 return Some((spec, None));
1033 }
1034 if let Some((x, residual, weight, denom)) = match_weighted_push(value) {
1035 let spec = FusedRowSpec {
1036 rows: rows as usize,
1037 cols: cols as usize,
1038 reduce: RowReduceKind::RmsNorm,
1039 x,
1040 y,
1041 weight: Some(weight),
1042 weight_rows: 1,
1043 residual,
1044 bias: None,
1045 eps: Epsilon::DEFAULT,
1046 };
1047 return Some((spec, Some(denom)));
1048 }
1049 }
1050 _ => {}
1051 }
1052 }
1053 None
1054}
1055
1056fn match_softmax_push(value: &Expr) -> Option<Symbol> {
1060 let (expo, s_denom) = as_binop(value, BinaryOpKind::Divide)?;
1061 as_ident(s_denom)?;
1062 let (base, shifted) = as_binop(expo, BinaryOpKind::Pow)?;
1063 if !is_euler(base) {
1064 return None;
1065 }
1066 let (x_index, m) = as_binop(shifted, BinaryOpKind::Subtract)?;
1067 as_ident(m)?;
1068 let (x_coll, _) = as_index(x_index)?;
1069 as_ident(x_coll)
1070}
1071
1072#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1075pub struct SwiGluSpec {
1076 pub total: usize,
1077 pub gate: Symbol,
1078 pub up: Symbol,
1079 pub y: Symbol,
1080}
1081
1082pub fn recognize_swiglu(
1085 stmts: &[Stmt],
1086 _interner: &Interner,
1087) -> Result<SwiGluSpec, ReductionReject> {
1088 let mut consts: HashMap<Symbol, i64> = HashMap::new();
1089 collect_consts_rec(stmts, &mut consts);
1090 search_swiglu(stmts, &consts, None).ok_or(ReductionReject::NotReduction)
1091}
1092
1093fn search_swiglu(
1094 stmts: &[Stmt],
1095 consts: &HashMap<Symbol, i64>,
1096 binder: Option<Symbol>,
1097) -> Option<SwiGluSpec> {
1098 for s in stmts {
1099 match s {
1100 Stmt::While { cond, body, .. } => {
1101 if let Some((iv, n)) = ew_loop_bound(cond, consts) {
1102 if n > 0 {
1103 for st in *body {
1104 if let Stmt::Push { value, collection } = st {
1105 let y = as_ident(collection)?;
1106 if !pushes_into_binder(y, binder) {
1107 continue;
1108 }
1109 if let Some((gate, up)) = match_swiglu_push(value, iv) {
1110 return Some(SwiGluSpec {
1111 total: n as usize,
1112 gate,
1113 up,
1114 y,
1115 });
1116 }
1117 }
1118 }
1119 }
1120 }
1121 if let Some(spec) = search_swiglu(body, consts, binder) {
1122 return Some(spec);
1123 }
1124 }
1125 Stmt::If {
1126 then_block,
1127 else_block,
1128 ..
1129 } => {
1130 if let Some(spec) = search_swiglu(then_block, consts, binder) {
1131 return Some(spec);
1132 }
1133 if let Some(eb) = else_block {
1134 if let Some(spec) = search_swiglu(eb, consts, binder) {
1135 return Some(spec);
1136 }
1137 }
1138 }
1139 Stmt::Splice { body } => {
1140 if let Some(spec) = search_swiglu(body, consts, splice_binder(body).or(binder)) {
1141 return Some(spec);
1142 }
1143 }
1144 _ => {}
1145 }
1146 }
1147 None
1148}
1149
1150fn match_swiglu_push(value: &Expr, iv: Symbol) -> Option<(Symbol, Symbol)> {
1152 let (silu, up_index) = as_binop(value, BinaryOpKind::Multiply)?;
1153 let (up_coll, up_idx) = as_index(up_index)?;
1154 if as_ident(up_idx) != Some(iv) {
1155 return None;
1156 }
1157 let (g_num, denom) = as_binop(silu, BinaryOpKind::Divide)?;
1159 let (g_coll, g_idx) = as_index(g_num)?;
1160 if as_ident(g_idx) != Some(iv) {
1161 return None;
1162 }
1163 let (_one, powe) = as_binop(denom, BinaryOpKind::Add)?;
1165 as_binop(powe, BinaryOpKind::Pow)?;
1166 Some((as_ident(g_coll)?, as_ident(up_coll)?))
1167}
1168
1169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1171pub enum ReductionReject {
1172 NotReduction,
1173}
1174
1175pub fn recognize_reduction(
1178 stmts: &[Stmt],
1179 _interner: &Interner,
1180) -> Result<ReductionSpec, ReductionReject> {
1181 let mut consts: HashMap<Symbol, i64> = HashMap::new();
1182 collect_consts_rec(stmts, &mut consts);
1183 search_reduction(stmts, &consts, None).ok_or(ReductionReject::NotReduction)
1184}
1185
1186fn search_reduction(
1187 stmts: &[Stmt],
1188 consts: &HashMap<Symbol, i64>,
1189 binder: Option<Symbol>,
1190) -> Option<ReductionSpec> {
1191 for s in stmts {
1192 match s {
1193 Stmt::While { cond, body, .. } => {
1194 if let Some(spec) = try_reduction_at(cond, body, consts, stmts, binder) {
1195 return Some(spec);
1196 }
1197 if let Some(spec) = search_reduction(body, consts, binder) {
1198 return Some(spec);
1199 }
1200 }
1201 Stmt::If {
1202 then_block,
1203 else_block,
1204 ..
1205 } => {
1206 if let Some(spec) = search_reduction(then_block, consts, binder) {
1207 return Some(spec);
1208 }
1209 if let Some(eb) = else_block {
1210 if let Some(spec) = search_reduction(eb, consts, binder) {
1211 return Some(spec);
1212 }
1213 }
1214 }
1215 Stmt::Splice { body } => {
1216 if let Some(spec) = search_reduction(body, consts, splice_binder(body).or(binder)) {
1217 return Some(spec);
1218 }
1219 }
1220 _ => {}
1221 }
1222 }
1223 None
1224}
1225
1226fn try_reduction_at(
1231 cond: &Expr,
1232 body: &[Stmt],
1233 consts: &HashMap<Symbol, i64>,
1234 scope: &[Stmt],
1235 binder: Option<Symbol>,
1236) -> Option<ReductionSpec> {
1237 let (iv, n) = ew_loop_bound(cond, consts)?;
1238 if n <= 0 {
1239 return None;
1240 }
1241 for st in body {
1242 let Stmt::Push { value, collection } = st else {
1243 continue;
1244 };
1245 let y = as_ident(collection)?;
1246 if !pushes_into_binder(y, binder) {
1247 continue;
1248 }
1249 let (num, den) = as_binop(value, BinaryOpKind::Divide)?;
1250 if let Some((coll, idx)) = as_index(num) {
1252 if as_ident(idx) == Some(iv)
1253 && as_ident(den).is_some_and(|d| binds_rms_denominator(scope, d))
1254 {
1255 return Some(ReductionSpec {
1256 kind: ReductionKind::Rmsnorm,
1257 x: as_ident(coll)?,
1258 y,
1259 n: n as usize,
1260 denom: as_ident(den),
1261 });
1262 }
1263 }
1264 if let Some((base, exponent)) = as_binop(num, BinaryOpKind::Pow) {
1266 if is_euler(base) && as_ident(den).is_some() {
1267 if let Some((subl, m)) = as_binop(exponent, BinaryOpKind::Subtract) {
1268 if as_ident(m).is_some() {
1269 if let Some((coll, idx)) = as_index(subl) {
1270 if as_ident(idx) == Some(iv) {
1271 return Some(ReductionSpec {
1272 kind: ReductionKind::Softmax,
1273 x: as_ident(coll)?,
1274 y,
1275 n: n as usize,
1276 denom: None,
1277 });
1278 }
1279 }
1280 }
1281 }
1282 }
1283 }
1284 }
1285 None
1286}
1287
1288fn splice_binder(body: &[Stmt]) -> Option<Symbol> {
1292 body.iter().find_map(|s| match s {
1293 Stmt::Let { var, value, .. } if matches!(value, Expr::New { .. }) => Some(*var),
1294 _ => None,
1295 })
1296}
1297
1298fn pushes_into_binder(collection: Symbol, binder: Option<Symbol>) -> bool {
1299 binder.map(|b| b == collection).unwrap_or(true)
1300}
1301
1302fn is_euler(e: &Expr) -> bool {
1303 matches!(e, Expr::Literal(Literal::Float(v)) if (*v - std::f64::consts::E).abs() < 1e-9)
1304}
1305
1306fn binds_rms_denominator(scope: &[Stmt], d: Symbol) -> bool {
1308 scope.iter().any(|s| match s {
1309 Stmt::Let {
1310 var,
1311 value: Expr::Call { args, .. },
1312 ..
1313 } if *var == d => match args.as_slice() {
1314 [arg] => as_binop(arg, BinaryOpKind::Add)
1315 .and_then(|(mean, eps)| Some((as_binop(mean, BinaryOpKind::Divide)?, eps)))
1316 .map(|((ss, _n), eps)| {
1317 as_ident(ss).is_some() && matches!(eps, Expr::Literal(Literal::Float(_)))
1318 })
1319 .unwrap_or(false),
1320 _ => false,
1321 },
1322 _ => false,
1323 })
1324}
1325
1326fn search_matmul(
1329 stmts: &[Stmt],
1330 consts: &HashMap<Symbol, i64>,
1331) -> Result<MatmulSpec, MatmulReject> {
1332 let mut best = MatmulReject::NoLoopNest;
1333 for s in stmts {
1334 match s {
1335 Stmt::While { cond, body, .. } => {
1336 match try_matmul_at(cond, body, consts) {
1337 Ok(spec) => return Ok(spec),
1338 Err(e) => {
1339 if reject_rank(&e) > reject_rank(&best) {
1340 best = e;
1341 }
1342 }
1343 }
1344 if let Ok(spec) = search_matmul(body, consts) {
1345 return Ok(spec);
1346 }
1347 }
1348 Stmt::If {
1349 then_block,
1350 else_block,
1351 ..
1352 } => {
1353 if let Ok(spec) = search_matmul(then_block, consts) {
1354 return Ok(spec);
1355 }
1356 if let Some(eb) = else_block {
1357 if let Ok(spec) = search_matmul(eb, consts) {
1358 return Ok(spec);
1359 }
1360 }
1361 }
1362 Stmt::Splice { body } => {
1365 if let Ok(spec) = search_matmul(body, consts) {
1366 return Ok(spec);
1367 }
1368 }
1369 _ => {}
1370 }
1371 }
1372 Err(best)
1373}
1374
1375fn try_matmul_at(
1377 cond_i: &Expr,
1378 body_i: &[Stmt],
1379 consts: &HashMap<Symbol, i64>,
1380) -> Result<MatmulSpec, MatmulReject> {
1381 let (iv_i, m) = loop_iv_bound(cond_i, consts).ok_or(MatmulReject::NoLoopNest)?;
1382 let (cond_k, body_k) = first_while(body_i).ok_or(MatmulReject::NotTripleNested)?;
1383 let (iv_k, k) = loop_iv_bound(cond_k, consts).ok_or(MatmulReject::NotTripleNested)?;
1384 let (cond_j, body_j) = first_while(body_k).ok_or(MatmulReject::NotTripleNested)?;
1385 let (iv_j, n) = loop_iv_bound(cond_j, consts).ok_or(MatmulReject::NotTripleNested)?;
1386
1387 let mut lets: HashMap<Symbol, Affine> = HashMap::new();
1390 for s in body_j {
1391 if let Stmt::Let { var, value, .. } = s {
1392 if let Some(a) = eval_affine(value, consts, &lets) {
1393 lets.insert(*var, a);
1394 }
1395 }
1396 }
1397
1398 let (store_coll, store_idx, store_val) = body_j
1400 .iter()
1401 .find_map(|s| match s {
1402 Stmt::SetIndex {
1403 collection,
1404 index,
1405 value,
1406 } => Some((*collection, *index, *value)),
1407 _ => None,
1408 })
1409 .ok_or(MatmulReject::NoAccumulatingStore)?;
1410
1411 let c = as_ident(store_coll).ok_or(MatmulReject::NotGemmLayout)?;
1412 let c_idx = eval_affine(store_idx, consts, &lets).ok_or(MatmulReject::NonAffineIndex)?;
1413
1414 let (add_l, add_r) =
1416 as_binop(store_val, BinaryOpKind::Add).ok_or(MatmulReject::NoAccumulatingStore)?;
1417 let (acc_coll, acc_idx) = as_index(add_l).ok_or(MatmulReject::NoAccumulatingStore)?;
1418 if as_ident(acc_coll) != Some(c) {
1419 return Err(MatmulReject::NoAccumulatingStore);
1420 }
1421 let acc_idx_aff = eval_affine(acc_idx, consts, &lets).ok_or(MatmulReject::NonAffineIndex)?;
1422 if acc_idx_aff != c_idx {
1423 return Err(MatmulReject::NoAccumulatingStore); }
1425
1426 let (mul_l, mul_r) =
1427 as_binop(add_r, BinaryOpKind::Multiply).ok_or(MatmulReject::NotGemmLayout)?;
1428 let (a_coll, a_idx_e) = as_index(mul_l).ok_or(MatmulReject::NotGemmLayout)?;
1429 let (b_coll, b_idx_e) = as_index(mul_r).ok_or(MatmulReject::NotGemmLayout)?;
1430 let a = as_ident(a_coll).ok_or(MatmulReject::NotGemmLayout)?;
1431 let b = as_ident(b_coll).ok_or(MatmulReject::NotGemmLayout)?;
1432 let a_idx = eval_affine(a_idx_e, consts, &lets).ok_or(MatmulReject::NonAffineIndex)?;
1433 let b_idx = eval_affine(b_idx_e, consts, &lets).ok_or(MatmulReject::NonAffineIndex)?;
1434
1435 let n_i = n;
1440 let k_i = k;
1441 let gemm_c =
1442 c_idx.coeff_of(iv_i) == n_i && c_idx.coeff_of(iv_j) == 1 && c_idx.coeff_of(iv_k) == 0;
1443 let gemm_a =
1444 a_idx.coeff_of(iv_i) == k_i && a_idx.coeff_of(iv_k) == 1 && a_idx.coeff_of(iv_j) == 0;
1445 let gemm_b =
1446 b_idx.coeff_of(iv_k) == n_i && b_idx.coeff_of(iv_j) == 1 && b_idx.coeff_of(iv_i) == 0;
1447 if !(gemm_c && gemm_a && gemm_b) {
1448 return Err(MatmulReject::NotGemmLayout);
1449 }
1450
1451 Ok(MatmulSpec {
1452 m: m as usize,
1453 n: n as usize,
1454 k: k as usize,
1455 a,
1456 b,
1457 c,
1458 })
1459}
1460
1461#[derive(Debug, Clone, PartialEq, Eq)]
1464pub struct DotSpec {
1465 pub n: usize,
1466 pub a: Symbol,
1467 pub b: Symbol,
1468 pub acc: Symbol,
1469}
1470
1471#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1476pub enum DotReject {
1477 NoLoop,
1479 NoAccumulation,
1481 NoProduct,
1483 NotInt,
1485 NonAffineIndex,
1487 NotAligned,
1489 NotCounted,
1491 ExtraWork,
1493 NotFromZero,
1495}
1496
1497#[derive(Debug, Clone, PartialEq, Eq)]
1500pub struct OuterSpec {
1501 pub m: usize,
1502 pub n: usize,
1503 pub a: Symbol,
1504 pub b: Symbol,
1505 pub c: Symbol,
1506}
1507
1508#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1510pub enum OuterReject {
1511 NoLoopNest,
1513 NotDoubleNested,
1515 NoStore,
1517 NoProduct,
1519 NotInt,
1521 Accumulates,
1523 Stores,
1525 NonAffineIndex,
1527 NotCounted,
1529 NotOuterLayout,
1531 ExtraWork,
1533}
1534
1535struct Counted {
1537 iv: Symbol,
1538 trips: i64,
1539}
1540
1541fn entry_values(
1545 stmts: &[Stmt],
1546 at: usize,
1547 consts: &HashMap<Symbol, i64>,
1548) -> HashMap<Symbol, Option<i64>> {
1549 let empty = HashMap::new();
1550 let mut out: HashMap<Symbol, Option<i64>> = HashMap::new();
1551 for s in stmts[..at].iter().rev() {
1552 let (var, value) = match s {
1553 Stmt::Let { var, value, .. } => (*var, *value),
1554 Stmt::Set { target, value } => (*target, *value),
1555 _ => break,
1556 };
1557 out.entry(var).or_insert_with(|| {
1558 eval_affine(value, consts, &empty)
1559 .filter(Affine::is_const)
1560 .map(|a| a.cst)
1561 });
1562 }
1563 out
1564}
1565
1566fn is_unit_step(s: &Stmt, iv: Symbol) -> bool {
1568 match s {
1569 Stmt::Set { target, value } if *target == iv => as_binop(value, BinaryOpKind::Add)
1570 .map_or(false, |(l, r)| {
1571 let one = |e: &Expr| matches!(e, Expr::Literal(Literal::Number(1)));
1572 (as_ident(l) == Some(iv) && one(r)) || (one(l) && as_ident(r) == Some(iv))
1573 }),
1574 _ => false,
1575 }
1576}
1577
1578fn counted_at(
1581 stmts: &[Stmt],
1582 at: usize,
1583 (iv, bound): (Symbol, i64),
1584 body: &[Stmt],
1585 consts: &HashMap<Symbol, i64>,
1586) -> Option<Counted> {
1587 let start = entry_values(stmts, at, consts).get(&iv).copied().flatten()?;
1588 let writes = |s: &&Stmt| {
1589 matches!(s, Stmt::Let { var, .. } if *var == iv)
1590 || matches!(s, Stmt::Set { target, .. } if *target == iv)
1591 };
1592 let steps = body.iter().filter(|s| is_unit_step(s, iv)).count();
1593 (steps == 1 && body.iter().filter(writes).count() == 1 && bound > start).then(|| Counted {
1594 iv,
1595 trips: bound - start,
1596 })
1597}
1598
1599fn search_loops<'a, T, R: Ord + Copy>(
1602 stmts: &'a [Stmt<'a>],
1603 floor: R,
1604 at: &impl Fn(&'a [Stmt<'a>], usize, &'a Expr<'a>, &'a [Stmt<'a>]) -> Result<T, R>,
1605) -> Result<T, R> {
1606 let mut best = floor;
1607 for (k, s) in stmts.iter().enumerate() {
1608 let nested: Vec<&'a [Stmt<'a>]> = match s {
1609 Stmt::While { cond, body, .. } => {
1610 match at(stmts, k, cond, body) {
1611 Ok(spec) => return Ok(spec),
1612 Err(e) => best = best.max(e),
1613 }
1614 vec![*body]
1615 }
1616 Stmt::If {
1617 then_block,
1618 else_block,
1619 ..
1620 } => std::iter::once(*then_block).chain(*else_block).collect(),
1621 Stmt::Splice { body } => vec![*body],
1622 _ => Vec::new(),
1623 };
1624 for block in nested {
1625 match search_loops(block, floor, at) {
1626 Ok(spec) => return Ok(spec),
1627 Err(e) => best = best.max(e),
1628 }
1629 }
1630 }
1631 Err(best)
1632}
1633
1634fn body_lets<'a>(
1637 scope: impl IntoIterator<Item = &'a Stmt<'a>>,
1638 consts: &HashMap<Symbol, i64>,
1639) -> (HashMap<Symbol, Affine>, HashMap<Symbol, &'a Expr<'a>>) {
1640 let mut affine = HashMap::new();
1641 let mut exprs = HashMap::new();
1642 for s in scope {
1643 if let Stmt::Let {
1644 var,
1645 value,
1646 mutable: false,
1647 ..
1648 } = s
1649 {
1650 if let Some(a) = eval_affine(value, consts, &affine) {
1651 affine.insert(*var, a);
1652 }
1653 exprs.insert(*var, *value);
1654 }
1655 }
1656 (affine, exprs)
1657}
1658
1659fn through_lets<'a>(e: &'a Expr<'a>, exprs: &HashMap<Symbol, &'a Expr<'a>>) -> &'a Expr<'a> {
1661 as_ident(e)
1662 .and_then(|s| exprs.get(&s).copied())
1663 .unwrap_or(e)
1664}
1665
1666fn indexed_product<'a>(
1668 e: &'a Expr<'a>,
1669 exprs: &HashMap<Symbol, &'a Expr<'a>>,
1670) -> Option<(Symbol, &'a Expr<'a>, Symbol, &'a Expr<'a>)> {
1671 let (l, r) = as_binop(through_lets(e, exprs), BinaryOpKind::Multiply)?;
1672 let (ac, ai) = as_index(through_lets(l, exprs))?;
1673 let (bc, bi) = as_index(through_lets(r, exprs))?;
1674 Some((as_ident(ac)?, ai, as_ident(bc)?, bi))
1675}
1676
1677fn is_body_let(s: &Stmt, kept: &[Symbol]) -> bool {
1679 matches!(s, Stmt::Let { var, mutable: false, .. } if !kept.contains(var))
1680}
1681
1682pub fn recognize_dot(stmts: &[Stmt], interner: &Interner) -> Result<DotSpec, DotReject> {
1697 let mut consts: HashMap<Symbol, i64> = HashMap::new();
1698 collect_consts_rec(stmts, &mut consts);
1699 let program = Program::of(stmts, interner);
1700 search_loops(stmts, DotReject::NoLoop, &|block, at, cond, body| {
1701 let zip = try_zip_at(block, at, cond, body, &consts, &program).map_err(DotReject::from)?;
1702 match zip.term {
1703 ZipTerm::Op(ZipOp::Mul, ref l, ref r) => match (l.as_ref(), r.as_ref()) {
1704 (ZipTerm::Read(x), ZipTerm::Read(y)) => Ok(DotSpec {
1705 n: zip.n,
1706 a: zip.operands[*x],
1707 b: zip.operands[*y],
1708 acc: zip.acc,
1709 }),
1710 _ => Err(DotReject::NoProduct),
1711 },
1712 _ => Err(DotReject::NoProduct),
1713 }
1714 })
1715}
1716
1717impl From<ZipReject> for DotReject {
1718 fn from(z: ZipReject) -> DotReject {
1719 match z {
1720 ZipReject::NoLoop => DotReject::NoLoop,
1721 ZipReject::NoAccumulation => DotReject::NoAccumulation,
1722 ZipReject::UnsupportedTerm => DotReject::NoProduct,
1723 ZipReject::NotInt => DotReject::NotInt,
1724 ZipReject::NonAffineIndex => DotReject::NonAffineIndex,
1725 ZipReject::NotAligned => DotReject::NotAligned,
1726 ZipReject::NotCounted => DotReject::NotCounted,
1727 ZipReject::ExtraWork => DotReject::ExtraWork,
1728 ZipReject::NotFromZero => DotReject::NotFromZero,
1729 }
1730 }
1731}
1732
1733#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1736pub enum ZipOp {
1737 Add,
1738 Sub,
1739 Mul,
1740 Div,
1741 Pow,
1742 Lt,
1743 Gt,
1744 Le,
1745 Ge,
1746 Eq,
1747 Ne,
1748 And,
1749 Or,
1750}
1751
1752impl ZipOp {
1753 fn of(op: BinaryOpKind) -> Option<ZipOp> {
1754 Some(match op {
1755 BinaryOpKind::Add => ZipOp::Add,
1756 BinaryOpKind::Subtract => ZipOp::Sub,
1757 BinaryOpKind::Multiply => ZipOp::Mul,
1758 BinaryOpKind::Divide => ZipOp::Div,
1759 BinaryOpKind::Pow => ZipOp::Pow,
1760 BinaryOpKind::Lt => ZipOp::Lt,
1761 BinaryOpKind::Gt => ZipOp::Gt,
1762 BinaryOpKind::LtEq => ZipOp::Le,
1763 BinaryOpKind::GtEq => ZipOp::Ge,
1764 BinaryOpKind::Eq => ZipOp::Eq,
1765 BinaryOpKind::NotEq => ZipOp::Ne,
1766 BinaryOpKind::And => ZipOp::And,
1767 BinaryOpKind::Or => ZipOp::Or,
1768 _ => return None,
1769 })
1770 }
1771
1772 pub fn compares(self) -> bool {
1774 matches!(self, ZipOp::Lt | ZipOp::Gt | ZipOp::Le | ZipOp::Ge | ZipOp::Eq | ZipOp::Ne)
1775 }
1776
1777 pub fn connects(self) -> bool {
1779 matches!(self, ZipOp::And | ZipOp::Or)
1780 }
1781}
1782
1783#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1785pub enum Ty {
1786 Int,
1787 Real,
1788 Bool,
1789}
1790
1791#[derive(Debug, Clone, Copy, PartialEq)]
1793pub enum Scalar {
1794 Int(i64),
1795 Real(f64),
1796 Bool(bool),
1797}
1798
1799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1802pub enum Stop {
1803 IntRange,
1804 RealRange,
1805 ZeroDivisor,
1806 NegativeExponent,
1807 Mistyped,
1808}
1809
1810#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1814pub enum ZipTerm {
1815 Read(usize),
1817 Const(i64),
1818 Real(u64),
1820 Op(ZipOp, Box<ZipTerm>, Box<ZipTerm>),
1821 Select(Box<ZipTerm>, Box<ZipTerm>, Box<ZipTerm>),
1823 Abs(Box<ZipTerm>),
1825}
1826
1827impl ZipTerm {
1828 pub fn real(v: f64) -> ZipTerm {
1830 ZipTerm::Real(v.to_bits())
1831 }
1832
1833 pub fn ty(&self, reads: &[Ty]) -> Option<Ty> {
1836 match self {
1837 ZipTerm::Read(k) => reads.get(*k).copied(),
1838 ZipTerm::Const(_) => Some(Ty::Int),
1839 ZipTerm::Real(_) => Some(Ty::Real),
1840 ZipTerm::Abs(x) => abs_ty(x.ty(reads)?),
1841 ZipTerm::Op(op, l, r) => op_ty(*op, l.ty(reads)?, r.ty(reads)?),
1842 ZipTerm::Select(c, a, b) => select_ty(c.ty(reads)?, a.ty(reads)?, b.ty(reads)?),
1843 }
1844 }
1845
1846 pub fn eval(&self, row: &[i64]) -> Option<i64> {
1849 self.eval_within(row, i64::MIN as i128, i64::MAX as i128)
1850 }
1851
1852 pub fn eval_within(&self, row: &[i64], lo: i128, hi: i128) -> Option<i64> {
1854 let row: Vec<Scalar> = row.iter().map(|v| Scalar::Int(*v)).collect();
1855 match self.value(&row, (lo, hi), f64::MAX).ok()? {
1856 Scalar::Int(v) => Some(v),
1857 Scalar::Bool(b) => Some(b as i64),
1858 Scalar::Real(_) => None,
1859 }
1860 }
1861
1862 pub fn value(&self, row: &[Scalar], ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
1866 match self {
1867 ZipTerm::Read(k) => bounded(*row.get(*k).ok_or(Stop::Mistyped)?, ints, reals),
1868 ZipTerm::Const(c) => bounded(Scalar::Int(*c), ints, reals),
1869 ZipTerm::Real(b) => bounded(Scalar::Real(f64::from_bits(*b)), ints, reals),
1870 ZipTerm::Abs(x) => abs_of(x.value(row, ints, reals)?, ints, reals),
1871 ZipTerm::Select(c, a, b) => match c.value(row, ints, reals)? {
1872 Scalar::Bool(true) => a.value(row, ints, reals),
1873 Scalar::Bool(false) => b.value(row, ints, reals),
1874 _ => Err(Stop::Mistyped),
1875 },
1876 ZipTerm::Op(op, l, r) => apply(*op, l.value(row, ints, reals)?, r.value(row, ints, reals)?, ints, reals),
1877 }
1878 }
1879}
1880
1881fn op_ty(op: ZipOp, a: Ty, b: Ty) -> Option<Ty> {
1883 let num = |t: Ty| t != Ty::Bool;
1884 if op.connects() {
1885 (a == Ty::Bool && b == Ty::Bool).then_some(Ty::Bool)
1886 } else if !(num(a) && num(b)) {
1887 None
1888 } else if op.compares() {
1889 Some(Ty::Bool)
1890 } else if a == Ty::Real || b == Ty::Real {
1891 Some(Ty::Real)
1892 } else {
1893 Some(Ty::Int)
1894 }
1895}
1896
1897fn select_ty(c: Ty, a: Ty, b: Ty) -> Option<Ty> {
1899 (c == Ty::Bool && a == b && a != Ty::Bool).then_some(a)
1900}
1901
1902fn abs_ty(x: Ty) -> Option<Ty> {
1903 (x != Ty::Bool).then_some(x)
1904}
1905
1906fn bounded(v: Scalar, ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
1908 match v {
1909 Scalar::Int(i) if !(ints.0 <= i as i128 && i as i128 <= ints.1) => Err(Stop::IntRange),
1910 Scalar::Real(r) if !(r.is_finite() && r.abs() <= reals) => Err(Stop::RealRange),
1911 v => Ok(v),
1912 }
1913}
1914
1915fn abs_of(v: Scalar, ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
1916 match v {
1917 Scalar::Int(i) => {
1918 let a = (i as i128).abs();
1919 if ints.0 <= a && a <= ints.1 {
1920 Ok(Scalar::Int(a as i64))
1921 } else {
1922 Err(Stop::IntRange)
1923 }
1924 }
1925 Scalar::Real(r) => bounded(Scalar::Real(r.abs()), ints, reals),
1926 Scalar::Bool(_) => Err(Stop::Mistyped),
1927 }
1928}
1929
1930fn apply(op: ZipOp, a: Scalar, b: Scalar, ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
1933 let int = |v: i128| {
1934 if ints.0 <= v && v <= ints.1 {
1935 Ok(Scalar::Int(v as i64))
1936 } else {
1937 Err(Stop::IntRange)
1938 }
1939 };
1940 let cmp = |o: std::cmp::Ordering| {
1941 use std::cmp::Ordering::*;
1942 Scalar::Bool(match op {
1943 ZipOp::Lt => o == Less,
1944 ZipOp::Gt => o == Greater,
1945 ZipOp::Le => o != Greater,
1946 ZipOp::Ge => o != Less,
1947 ZipOp::Eq => o == Equal,
1948 _ => o != Equal,
1949 })
1950 };
1951 match (a, b) {
1952 (Scalar::Bool(x), Scalar::Bool(y)) => match op {
1953 ZipOp::And => Ok(Scalar::Bool(x && y)),
1954 ZipOp::Or => Ok(Scalar::Bool(x || y)),
1955 _ => Err(Stop::Mistyped),
1956 },
1957 (Scalar::Bool(_), _) | (_, Scalar::Bool(_)) => Err(Stop::Mistyped),
1958 _ if op.connects() => Err(Stop::Mistyped),
1959 (Scalar::Int(x), Scalar::Int(y)) => {
1960 let (x, y) = (x as i128, y as i128);
1961 match op {
1962 ZipOp::Add => int(x + y),
1963 ZipOp::Sub => int(x - y),
1964 ZipOp::Mul => int(x * y),
1965 ZipOp::Div if y == 0 => Err(Stop::ZeroDivisor),
1966 ZipOp::Div => int(x / y),
1967 ZipOp::Pow if y < 0 => Err(Stop::NegativeExponent),
1968 ZipOp::Pow => u32::try_from(y).ok().and_then(|e| x.checked_pow(e)).ok_or(Stop::IntRange).and_then(int),
1969 _ => Ok(cmp(x.cmp(&y))),
1970 }
1971 }
1972 (a, b) => {
1973 let f = |v: Scalar| match v {
1974 Scalar::Int(i) => i as f64,
1975 Scalar::Real(r) => r,
1976 Scalar::Bool(_) => unreachable!("booleans are refused above"),
1977 };
1978 let (x, y) = (f(a), f(b));
1979 let real = |v: f64| bounded(Scalar::Real(v), ints, reals);
1980 match op {
1981 ZipOp::Add => real(x + y),
1982 ZipOp::Sub => real(x - y),
1983 ZipOp::Mul => real(x * y),
1984 ZipOp::Div => real(x / y),
1985 ZipOp::Pow => real(x.powf(y)),
1986 _ => x.partial_cmp(&y).map(cmp).ok_or(Stop::RealRange),
1987 }
1988 }
1989 }
1990}
1991
1992#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1994pub enum DagNode {
1995 Read(usize),
1996 Const(i64),
1997 Real(u64),
1998 Op(ZipOp, usize, usize),
1999 Select(usize, usize, usize),
2000 Abs(usize),
2001}
2002
2003#[derive(Debug, Clone, PartialEq, Eq)]
2006pub struct TermDag {
2007 pub nodes: Vec<DagNode>,
2008 pub uses: Vec<usize>,
2010 pub root: usize,
2011}
2012
2013impl TermDag {
2014 pub fn of(term: &ZipTerm) -> TermDag {
2015 fn intern(t: &ZipTerm, nodes: &mut Vec<DagNode>, index: &mut HashMap<DagNode, usize>) -> usize {
2016 let mut child = |c: &ZipTerm| intern(c, nodes, index);
2017 let node = match t {
2018 ZipTerm::Read(k) => DagNode::Read(*k),
2019 ZipTerm::Const(c) => DagNode::Const(*c),
2020 ZipTerm::Real(b) => DagNode::Real(*b),
2021 ZipTerm::Abs(x) => DagNode::Abs(child(x)),
2022 ZipTerm::Op(op, l, r) => {
2023 let l = child(l);
2024 DagNode::Op(*op, l, child(r))
2025 }
2026 ZipTerm::Select(c, a, b) => {
2027 let (c, a) = (child(c), child(a));
2028 DagNode::Select(c, a, child(b))
2029 }
2030 };
2031 *index.entry(node).or_insert_with(|| {
2032 nodes.push(node);
2033 nodes.len() - 1
2034 })
2035 }
2036 let (mut nodes, mut index) = (Vec::new(), HashMap::new());
2037 let root = intern(term, &mut nodes, &mut index);
2038 let mut uses = vec![0; nodes.len()];
2039 for n in &nodes {
2040 match *n {
2041 DagNode::Abs(x) => uses[x] += 1,
2042 DagNode::Op(_, l, r) => {
2043 uses[l] += 1;
2044 uses[r] += 1;
2045 }
2046 DagNode::Select(c, a, b) => {
2047 uses[c] += 1;
2048 uses[a] += 1;
2049 uses[b] += 1;
2050 }
2051 _ => {}
2052 }
2053 }
2054 TermDag { nodes, uses, root }
2055 }
2056
2057 pub fn types(&self, reads: &[Ty]) -> Option<Vec<Ty>> {
2060 let mut tys: Vec<Ty> = Vec::with_capacity(self.nodes.len());
2061 for n in &self.nodes {
2062 let t = match *n {
2063 DagNode::Read(k) => reads.get(k).copied(),
2064 DagNode::Const(_) => Some(Ty::Int),
2065 DagNode::Real(_) => Some(Ty::Real),
2066 DagNode::Abs(x) => abs_ty(tys[x]),
2067 DagNode::Op(op, l, r) => op_ty(op, tys[l], tys[r]),
2068 DagNode::Select(c, a, b) => select_ty(tys[c], tys[a], tys[b]),
2069 }?;
2070 tys.push(t);
2071 }
2072 Some(tys)
2073 }
2074
2075 pub fn value(&self, row: &[Scalar], ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
2078 self.value_of(self.root, row, ints, reals)
2079 }
2080
2081 pub fn value_of(&self, node: usize, row: &[Scalar], ints: (i128, i128), reals: f64) -> Result<Scalar, Stop> {
2083 fn at(
2084 d: &TermDag,
2085 i: usize,
2086 row: &[Scalar],
2087 ints: (i128, i128),
2088 reals: f64,
2089 memo: &mut Vec<Option<Result<Scalar, Stop>>>,
2090 ) -> Result<Scalar, Stop> {
2091 if let Some(v) = memo[i] {
2092 return v;
2093 }
2094 let v = match d.nodes[i] {
2095 DagNode::Read(k) => row.get(k).copied().ok_or(Stop::Mistyped).and_then(|v| bounded(v, ints, reals)),
2096 DagNode::Const(c) => bounded(Scalar::Int(c), ints, reals),
2097 DagNode::Real(b) => bounded(Scalar::Real(f64::from_bits(b)), ints, reals),
2098 DagNode::Abs(x) => at(d, x, row, ints, reals, memo).and_then(|v| abs_of(v, ints, reals)),
2099 DagNode::Op(op, l, r) => at(d, l, row, ints, reals, memo)
2100 .and_then(|a| Ok((a, at(d, r, row, ints, reals, memo)?)))
2101 .and_then(|(a, b)| apply(op, a, b, ints, reals)),
2102 DagNode::Select(c, a, b) => match at(d, c, row, ints, reals, memo) {
2103 Ok(Scalar::Bool(true)) => at(d, a, row, ints, reals, memo),
2104 Ok(Scalar::Bool(false)) => at(d, b, row, ints, reals, memo),
2105 Ok(_) => Err(Stop::Mistyped),
2106 Err(e) => Err(e),
2107 },
2108 };
2109 memo[i] = Some(v);
2110 v
2111 }
2112 at(self, node, row, ints, reals, &mut vec![None; self.nodes.len()])
2113 }
2114}
2115
2116pub struct Program<'a> {
2119 fns: HashMap<Symbol, PureFn<'a>>,
2120 abs: Option<Symbol>,
2121 elems: HashMap<Symbol, Option<Ty>>,
2123 prims: [(Option<Symbol>, Ty); 3],
2124}
2125
2126struct PureFn<'a> {
2128 params: Vec<(Symbol, Ty)>,
2129 ret: Ty,
2130 body: &'a [Stmt<'a>],
2131}
2132
2133impl<'a> Program<'a> {
2134 pub fn of(stmts: &'a [Stmt<'a>], interner: &Interner) -> Program<'a> {
2135 let prims = [
2136 (interner.lookup("Int"), Ty::Int),
2137 (interner.lookup("Real"), Ty::Real),
2138 (interner.lookup("Bool"), Ty::Bool),
2139 ];
2140 let mut program = Program { fns: HashMap::new(), abs: interner.lookup("abs"), elems: HashMap::new(), prims };
2141 let seqs = [interner.lookup("Seq"), interner.lookup("List")];
2142 for s in stmts {
2143 if let Stmt::FunctionDef { name, generics, params, body, return_type: Some(ret), is_native: false, .. } = s {
2144 let typed: Option<Vec<(Symbol, Ty)>> =
2145 params.iter().map(|(p, t)| program.ty_of(t).map(|ty| (*p, ty))).collect();
2146 if let (true, Some(params), Some(ret)) = (generics.is_empty(), typed, program.ty_of(ret)) {
2147 program.fns.insert(*name, PureFn { params, ret, body });
2148 }
2149 }
2150 }
2151 fn declare<'a>(program: &mut Program<'a>, seqs: &[Option<Symbol>; 2], stmts: &'a [Stmt<'a>]) {
2152 for s in stmts {
2153 match s {
2154 Stmt::Let { var, ty, value, .. } => {
2155 let elem = match (ty, value) {
2156 (Some(TypeExpr::Generic { base, params: [t] }), _) if seqs.contains(&Some(*base)) => {
2157 program.ty_of(t)
2158 }
2159 (_, Expr::New { type_name, type_args, .. }) if seqs.contains(&Some(*type_name)) => {
2160 match type_args.as_slice() {
2161 [t] => program.ty_of(t),
2162 _ => None,
2163 }
2164 }
2165 _ => continue,
2166 };
2167 let entry = program.elems.entry(*var).or_insert(elem);
2168 if *entry != elem {
2169 *entry = None;
2170 }
2171 }
2172 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
2173 declare(program, seqs, body)
2174 }
2175 Stmt::If { then_block, else_block, .. } => {
2176 declare(program, seqs, then_block);
2177 declare(program, seqs, else_block.unwrap_or(&[]));
2178 }
2179 _ => {}
2180 }
2181 }
2182 }
2183 declare(&mut program, &seqs, stmts);
2184 program
2185 }
2186
2187 fn ty_of(&self, t: &TypeExpr) -> Option<Ty> {
2189 match t {
2190 TypeExpr::Primitive(s) | TypeExpr::Named(s) => {
2191 self.prims.iter().find(|(p, _)| *p == Some(*s)).map(|(_, ty)| *ty)
2192 }
2193 _ => None,
2194 }
2195 }
2196
2197 pub fn elem(&self, array: Symbol) -> Option<Ty> {
2199 self.elems.get(&array).copied().flatten()
2200 }
2201}
2202
2203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2206enum TermReject {
2207 Unsupported,
2208 NonAffine,
2209 Layout,
2210}
2211
2212const MAX_TERM_NODES: usize = 1 << 20;
2214
2215struct Terms<'c, 'a> {
2220 program: &'c Program<'a>,
2221 consts: &'c HashMap<Symbol, i64>,
2222 exprs: &'c HashMap<Symbol, &'a Expr<'a>>,
2223 hidden: Vec<Symbol>,
2225 types: Vec<Option<Ty>>,
2227 nodes: usize,
2228}
2229
2230type Read<'r, 'a> = dyn FnMut(Symbol, &'a Expr<'a>) -> Result<usize, TermReject> + 'r;
2231
2232impl<'c, 'a> Terms<'c, 'a> {
2233 fn new(
2234 program: &'c Program<'a>,
2235 consts: &'c HashMap<Symbol, i64>,
2236 exprs: &'c HashMap<Symbol, &'a Expr<'a>>,
2237 hidden: Vec<Symbol>,
2238 ) -> Self {
2239 Terms { program, consts, exprs, hidden, types: Vec::new(), nodes: 0 }
2240 }
2241
2242 fn term(&mut self, e: &'a Expr<'a>, read: &mut Read<'_, 'a>) -> Result<ZipTerm, TermReject> {
2243 self.within(e, None, read, &mut Vec::new())
2244 }
2245
2246 fn types(&self) -> Option<Vec<Ty>> {
2248 self.types.iter().copied().collect()
2249 }
2250
2251 fn ty(&self, t: &ZipTerm) -> Option<Ty> {
2253 t.ty(&self.types.iter().map(|t| t.unwrap_or(Ty::Bool)).collect::<Vec<_>>())
2254 }
2255
2256 fn within(
2258 &mut self,
2259 e: &'a Expr<'a>,
2260 env: Option<&HashMap<Symbol, ZipTerm>>,
2261 read: &mut Read<'_, 'a>,
2262 calls: &mut Vec<Symbol>,
2263 ) -> Result<ZipTerm, TermReject> {
2264 self.nodes += 1;
2265 if self.nodes > MAX_TERM_NODES {
2266 return Err(TermReject::Unsupported);
2267 }
2268 let e = match env {
2269 Some(env) => match as_ident(e) {
2270 Some(s) => return env.get(&s).cloned().ok_or(TermReject::Unsupported),
2271 None => e,
2272 },
2273 None => through_lets(e, self.exprs),
2274 };
2275 match e {
2276 Expr::Literal(Literal::Number(n)) => Ok(ZipTerm::Const(*n)),
2277 Expr::Literal(Literal::Float(f)) => Ok(ZipTerm::real(*f)),
2278 Expr::Identifier(s) => self
2279 .consts
2280 .get(s)
2281 .filter(|_| !self.hidden.contains(s))
2282 .map(|c| ZipTerm::Const(*c))
2283 .ok_or(TermReject::Unsupported),
2284 Expr::Index { collection, index } if env.is_none() => {
2285 let array = as_ident(collection).ok_or(TermReject::Unsupported)?;
2286 let ty = self.program.elem(array).ok_or(TermReject::Unsupported)?;
2287 let k = read(array, index)?;
2288 if self.types.len() <= k {
2289 self.types.resize(k + 1, None);
2290 }
2291 self.types[k] = Some(ty);
2292 Ok(ZipTerm::Read(k))
2293 }
2294 Expr::BinaryOp { op, left, right } => {
2295 let op = ZipOp::of(*op).ok_or(TermReject::Unsupported)?;
2296 let l = self.within(left, env, read, calls)?;
2297 let r = self.within(right, env, read, calls)?;
2298 Ok(ZipTerm::Op(op, Box::new(l), Box::new(r)))
2299 }
2300 Expr::Call { function, args } => self.call(*function, args, env, read, calls),
2301 _ => Err(TermReject::Unsupported),
2302 }
2303 }
2304
2305 fn call(
2308 &mut self,
2309 function: Symbol,
2310 args: &[&'a Expr<'a>],
2311 env: Option<&HashMap<Symbol, ZipTerm>>,
2312 read: &mut Read<'_, 'a>,
2313 calls: &mut Vec<Symbol>,
2314 ) -> Result<ZipTerm, TermReject> {
2315 let program = self.program;
2316 if let Some(f) = program.fns.get(&function) {
2317 if calls.contains(&function) || args.len() != f.params.len() {
2318 return Err(TermReject::Unsupported);
2319 }
2320 let mut bound = HashMap::new();
2321 for ((p, want), a) in f.params.iter().zip(args) {
2322 let t = self.within(a, env, read, calls)?;
2323 if self.ty(&t) != Some(*want) {
2324 return Err(TermReject::Unsupported);
2325 }
2326 bound.insert(*p, t);
2327 }
2328 calls.push(function);
2329 let body = self.body(f.body.iter().collect(), bound, read, calls);
2330 calls.pop();
2331 let body = body?;
2332 if self.ty(&body) != Some(f.ret) {
2333 return Err(TermReject::Unsupported);
2334 }
2335 Ok(body)
2336 } else if Some(function) == program.abs && args.len() == 1 {
2337 Ok(ZipTerm::Abs(Box::new(self.within(args[0], env, read, calls)?)))
2338 } else {
2339 Err(TermReject::Unsupported)
2340 }
2341 }
2342
2343 fn body(
2347 &mut self,
2348 stmts: Vec<&'a Stmt<'a>>,
2349 mut env: HashMap<Symbol, ZipTerm>,
2350 read: &mut Read<'_, 'a>,
2351 calls: &mut Vec<Symbol>,
2352 ) -> Result<ZipTerm, TermReject> {
2353 for (k, s) in stmts.iter().enumerate() {
2354 match s {
2355 Stmt::Let { var, ty, value, mutable: false } => {
2356 let t = self.within(value, Some(&env), read, calls)?;
2357 if let Some(declared) = ty {
2358 if self.program.ty_of(declared).is_none() || self.program.ty_of(declared) != self.ty(&t) {
2359 return Err(TermReject::Unsupported);
2360 }
2361 }
2362 env.insert(*var, t);
2363 }
2364 Stmt::Return { value: Some(v) } => return self.within(v, Some(&env), read, calls),
2365 Stmt::If { cond, then_block, else_block } => {
2366 let c = self.within(cond, Some(&env), read, calls)?;
2367 let rest = &stmts[k + 1..];
2368 let path = |arm: &'a [Stmt<'a>]| arm.iter().chain(rest.iter().copied()).collect::<Vec<_>>();
2369 let a = self.body(path(then_block), env.clone(), read, calls)?;
2370 let b = self.body(path(else_block.unwrap_or(&[])), env.clone(), read, calls)?;
2371 return Ok(ZipTerm::Select(Box::new(c), Box::new(a), Box::new(b)));
2372 }
2373 _ => return Err(TermReject::Unsupported),
2374 }
2375 }
2376 Err(TermReject::Unsupported)
2377 }
2378}
2379
2380struct Aligned<'c> {
2382 iv: Symbol,
2383 consts: &'c HashMap<Symbol, i64>,
2384 lets: &'c HashMap<Symbol, Affine>,
2385 operands: Vec<Symbol>,
2386 index: Option<Affine>,
2387}
2388
2389impl<'c> Aligned<'c> {
2390 fn read(&mut self, array: Symbol, index: &Expr) -> Result<usize, TermReject> {
2391 let at = eval_affine(index, self.consts, self.lets).ok_or(TermReject::NonAffine)?;
2392 if at.coeff.len() != 1 || at.coeff_of(self.iv) != 1 || self.index.as_ref().map_or(false, |i| *i != at) {
2393 return Err(TermReject::Layout);
2394 }
2395 self.index = Some(at);
2396 Ok(match self.operands.iter().position(|o| *o == array) {
2397 Some(k) => k,
2398 None => {
2399 self.operands.push(array);
2400 self.operands.len() - 1
2401 }
2402 })
2403 }
2404}
2405
2406#[derive(Debug, Clone, PartialEq, Eq)]
2410pub struct ZipSum {
2411 pub n: usize,
2412 pub operands: Vec<Symbol>,
2414 pub term: ZipTerm,
2415 pub acc: Symbol,
2416}
2417
2418#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2421pub enum ZipReject {
2422 NoLoop,
2424 NoAccumulation,
2426 UnsupportedTerm,
2430 NotInt,
2432 NonAffineIndex,
2434 NotAligned,
2436 NotCounted,
2438 ExtraWork,
2440 NotFromZero,
2442}
2443
2444impl From<TermReject> for ZipReject {
2445 fn from(r: TermReject) -> ZipReject {
2446 match r {
2447 TermReject::Unsupported => ZipReject::UnsupportedTerm,
2448 TermReject::NonAffine => ZipReject::NonAffineIndex,
2449 TermReject::Layout => ZipReject::NotAligned,
2450 }
2451 }
2452}
2453
2454pub fn recognize_zip_sum(stmts: &[Stmt], interner: &Interner) -> Result<ZipSum, ZipReject> {
2472 recognize_zip_sum_where(stmts, interner, &|_| true)
2473}
2474
2475pub fn recognize_zip_sum_where(
2478 stmts: &[Stmt],
2479 interner: &Interner,
2480 wanted: &dyn Fn(&ZipSum) -> bool,
2481) -> Result<ZipSum, ZipReject> {
2482 let mut consts: HashMap<Symbol, i64> = HashMap::new();
2483 collect_consts_rec(stmts, &mut consts);
2484 let program = Program::of(stmts, interner);
2485 search_loops(stmts, ZipReject::NoLoop, &|block, at, cond, body| {
2486 try_zip_at(block, at, cond, body, &consts, &program)
2487 .and_then(|z| if wanted(&z) { Ok(z) } else { Err(ZipReject::UnsupportedTerm) })
2488 })
2489}
2490
2491fn accumulation_into<'a>(s: &'a Stmt<'a>, iv: Symbol) -> Option<(Symbol, &'a Expr<'a>)> {
2493 match s {
2494 Stmt::Set { target, value } if *target != iv => {
2495 let (l, r) = as_binop(value, BinaryOpKind::Add)?;
2496 if as_ident(l) == Some(*target) {
2497 Some((*target, r))
2498 } else if as_ident(r) == Some(*target) {
2499 Some((*target, l))
2500 } else {
2501 None
2502 }
2503 }
2504 _ => None,
2505 }
2506}
2507
2508fn accumulation_of<'a>(
2511 s: &'a Stmt<'a>,
2512 iv: Symbol,
2513) -> Option<(Symbol, Option<&'a Expr<'a>>, &'a Expr<'a>, Option<&'a Expr<'a>>)> {
2514 if let Some((acc, e)) = accumulation_into(s, iv) {
2515 return Some((acc, None, e, None));
2516 }
2517 let Stmt::If { cond, then_block, else_block } = s else { return None };
2518 let [then] = then_block else { return None };
2519 let (acc, e1) = accumulation_into(then, iv)?;
2520 let e2 = match else_block {
2521 None => None,
2522 Some([other]) => match accumulation_into(other, iv)? {
2523 (a2, e2) if a2 == acc => Some(e2),
2524 _ => return None,
2525 },
2526 Some(_) => return None,
2527 };
2528 Some((acc, Some(*cond), e1, e2))
2529}
2530
2531fn try_zip_at<'a>(
2532 stmts: &'a [Stmt<'a>],
2533 at: usize,
2534 cond: &'a Expr<'a>,
2535 body: &'a [Stmt<'a>],
2536 consts: &HashMap<Symbol, i64>,
2537 program: &Program<'a>,
2538) -> Result<ZipSum, ZipReject> {
2539 let bound = loop_iv_bound(cond, consts).ok_or(ZipReject::NoLoop)?;
2540 let iv = bound.0;
2541 let (lets, exprs) = body_lets(body, consts);
2542 let (pos, (acc, guard, then, otherwise)) = body
2543 .iter()
2544 .enumerate()
2545 .find_map(|(k, s)| accumulation_of(s, iv).map(|a| (k, a)))
2546 .ok_or(ZipReject::NoAccumulation)?;
2547 let mut aligned = Aligned { iv, consts, lets: &lets, operands: Vec::new(), index: None };
2549 let mut terms = Terms::new(program, consts, &exprs, vec![iv, acc]);
2550 let mut read = |a, i| aligned.read(a, i);
2551 let term = match guard {
2552 None => terms.term(then, &mut read)?,
2553 Some(g) => {
2554 let g = terms.term(g, &mut read)?;
2555 let value = terms.term(then, &mut read)?;
2556 let e = match otherwise {
2557 Some(e) => terms.term(e, &mut read)?,
2558 None => ZipTerm::Const(0),
2559 };
2560 ZipTerm::Select(Box::new(g), Box::new(value), Box::new(e))
2561 }
2562 };
2563 let types = terms.types().ok_or(ZipReject::UnsupportedTerm)?;
2564 match term.ty(&types) {
2565 None | Some(Ty::Bool) => return Err(ZipReject::UnsupportedTerm),
2566 Some(Ty::Real) => return Err(ZipReject::NotInt),
2567 Some(Ty::Int) if types.iter().any(|t| *t != Ty::Int) => return Err(ZipReject::NotInt),
2568 Some(Ty::Int) => {}
2569 }
2570 let operands = aligned.operands;
2571 let loop_ = counted_at(stmts, at, bound, body, consts).ok_or(ZipReject::NotCounted)?;
2572 let mut kept = vec![iv, acc];
2573 kept.extend(&operands);
2574 if body
2575 .iter()
2576 .enumerate()
2577 .any(|(k, s)| !(k == pos || is_unit_step(s, iv) || is_body_let(s, &kept)))
2578 {
2579 return Err(ZipReject::ExtraWork);
2580 }
2581 if entry_values(stmts, at, consts).get(&acc).copied().flatten() != Some(0) {
2582 return Err(ZipReject::NotFromZero);
2583 }
2584 Ok(ZipSum {
2585 n: loop_.trips as usize,
2586 operands,
2587 term,
2588 acc,
2589 })
2590}
2591
2592#[derive(Debug, Clone, PartialEq, Eq)]
2596pub struct ZipMap {
2597 pub n: usize,
2598 pub operands: Vec<Symbol>,
2600 pub types: Vec<Ty>,
2602 pub term: ZipTerm,
2603 pub ty: Ty,
2605 pub out: Symbol,
2606}
2607
2608#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2610pub enum ZipMapReject {
2611 NoLoop,
2613 NoStore,
2615 UnsupportedTerm,
2619 NonAffineIndex,
2621 NotAligned,
2623 NotCounted,
2625 ExtraWork,
2627}
2628
2629impl From<TermReject> for ZipMapReject {
2630 fn from(r: TermReject) -> ZipMapReject {
2631 match r {
2632 TermReject::Unsupported => ZipMapReject::UnsupportedTerm,
2633 TermReject::NonAffine => ZipMapReject::NonAffineIndex,
2634 TermReject::Layout => ZipMapReject::NotAligned,
2635 }
2636 }
2637}
2638
2639pub fn recognize_zip_map(stmts: &[Stmt], interner: &Interner) -> Result<ZipMap, ZipMapReject> {
2653 recognize_zip_map_where(stmts, interner, &|_| true)
2654}
2655
2656pub fn recognize_zip_map_where(
2659 stmts: &[Stmt],
2660 interner: &Interner,
2661 wanted: &dyn Fn(&ZipMap) -> bool,
2662) -> Result<ZipMap, ZipMapReject> {
2663 let mut consts: HashMap<Symbol, i64> = HashMap::new();
2664 collect_consts_rec(stmts, &mut consts);
2665 let program = Program::of(stmts, interner);
2666 search_loops(stmts, ZipMapReject::NoLoop, &|block, at, cond, body| {
2667 try_map_at(block, at, cond, body, &consts, &program)
2668 .and_then(|z| if wanted(&z) { Ok(z) } else { Err(ZipMapReject::UnsupportedTerm) })
2669 })
2670}
2671
2672fn try_map_at<'a>(
2673 stmts: &'a [Stmt<'a>],
2674 at: usize,
2675 cond: &'a Expr<'a>,
2676 body: &'a [Stmt<'a>],
2677 consts: &HashMap<Symbol, i64>,
2678 program: &Program<'a>,
2679) -> Result<ZipMap, ZipMapReject> {
2680 let bound = loop_iv_bound(cond, consts).ok_or(ZipMapReject::NoLoop)?;
2681 let iv = bound.0;
2682 let (lets, exprs) = body_lets(body, consts);
2683 let (pos, out, index, value) = body
2684 .iter()
2685 .enumerate()
2686 .find_map(|(k, s)| match s {
2687 Stmt::SetIndex { collection, index, value } => Some((k, as_ident(collection)?, *index, *value)),
2688 _ => None,
2689 })
2690 .ok_or(ZipMapReject::NoStore)?;
2691 let mut aligned = Aligned { iv, consts, lets: &lets, operands: Vec::new(), index: None };
2692 let mut terms = Terms::new(program, consts, &exprs, vec![iv, out]);
2693 let term = terms.term(value, &mut |a, i| aligned.read(a, i))?;
2694 let types = terms.types().ok_or(ZipMapReject::UnsupportedTerm)?;
2695 let ty = term.ty(&types).filter(|t| *t != Ty::Bool).ok_or(ZipMapReject::UnsupportedTerm)?;
2696 if aligned.operands.is_empty() || aligned.operands.contains(&out) || program.elem(out) != Some(ty) {
2697 return Err(ZipMapReject::UnsupportedTerm);
2698 }
2699 let stored = eval_affine(index, consts, &lets).ok_or(ZipMapReject::NonAffineIndex)?;
2700 if aligned.index.as_ref() != Some(&stored) {
2701 return Err(ZipMapReject::NotAligned);
2702 }
2703 let loop_ = counted_at(stmts, at, bound, body, consts).ok_or(ZipMapReject::NotCounted)?;
2704 let mut kept = vec![iv, out];
2705 kept.extend(&aligned.operands);
2706 if body
2707 .iter()
2708 .enumerate()
2709 .any(|(k, s)| !(k == pos || is_unit_step(s, iv) || is_body_let(s, &kept)))
2710 {
2711 return Err(ZipMapReject::ExtraWork);
2712 }
2713 Ok(ZipMap {
2714 n: loop_.trips as usize,
2715 operands: aligned.operands,
2716 types,
2717 term,
2718 ty,
2719 out,
2720 })
2721}
2722
2723pub fn recognize_outer(stmts: &[Stmt], interner: &Interner) -> Result<OuterSpec, OuterReject> {
2740 let mut consts: HashMap<Symbol, i64> = HashMap::new();
2741 collect_consts_rec(stmts, &mut consts);
2742 let program = Program::of(stmts, interner);
2743 search_loops(stmts, OuterReject::NoLoopNest, &|block, at, cond, body| {
2744 outer_nest_at(block, at, cond, body, &consts, &program, false)
2745 })
2746}
2747
2748fn index_read_of<'a>(e: &'a Expr<'a>, array: Symbol) -> Option<&'a Expr<'a>> {
2750 as_index(e).filter(|(coll, _)| as_ident(coll) == Some(array)).map(|(_, i)| i)
2751}
2752
2753fn outer_nest_at(
2756 stmts: &[Stmt],
2757 at: usize,
2758 cond_i: &Expr,
2759 body_i: &[Stmt],
2760 consts: &HashMap<Symbol, i64>,
2761 program: &Program,
2762 accumulate: bool,
2763) -> Result<OuterSpec, OuterReject> {
2764 let bound_i = loop_iv_bound(cond_i, consts).ok_or(OuterReject::NoLoopNest)?;
2765 let (at_j, cond_j, body_j) = body_i
2766 .iter()
2767 .enumerate()
2768 .find_map(|(k, s)| match s {
2769 Stmt::While { cond, body, .. } => Some((k, *cond, *body)),
2770 _ => None,
2771 })
2772 .ok_or(OuterReject::NotDoubleNested)?;
2773 let bound_j = loop_iv_bound(cond_j, consts).ok_or(OuterReject::NotDoubleNested)?;
2774 let (lets, exprs) = body_lets(body_i[..at_j].iter().chain(body_j), consts);
2775
2776 let (store_coll, store_idx, value) = body_j
2777 .iter()
2778 .find_map(|s| match s {
2779 Stmt::SetIndex {
2780 collection,
2781 index,
2782 value,
2783 } => Some((*collection, *index, *value)),
2784 _ => None,
2785 })
2786 .ok_or(OuterReject::NoStore)?;
2787 let c = as_ident(store_coll).ok_or(OuterReject::NoStore)?;
2788 let value = through_lets(value, &exprs);
2789 let accumulated = as_binop(value, BinaryOpKind::Add).and_then(|(l, r)| {
2791 match (index_read_of(l, c), index_read_of(r, c)) {
2792 (Some(ri), _) if indexed_product(r, &exprs).is_some() => Some((ri, r)),
2793 (_, Some(ri)) if indexed_product(l, &exprs).is_some() => Some((ri, l)),
2794 _ => None,
2795 }
2796 });
2797 let value = match (accumulated, accumulate) {
2798 (Some(_), false) => return Err(OuterReject::Accumulates),
2799 (None, true) => {
2800 return Err(if indexed_product(value, &exprs).is_some() { OuterReject::Stores } else { OuterReject::NoProduct })
2801 }
2802 (Some((read_index, product)), true) => {
2803 let ri = eval_affine(read_index, consts, &lets).ok_or(OuterReject::NonAffineIndex)?;
2804 if Some(ri) != eval_affine(store_idx, consts, &lets) {
2805 return Err(OuterReject::NotOuterLayout);
2806 }
2807 product
2808 }
2809 (None, false) => value,
2810 };
2811 let (a, ai, b, bi) = indexed_product(value, &exprs).ok_or(OuterReject::NoProduct)?;
2812 if [a, b, c].iter().any(|x| program.elem(*x) != Some(Ty::Int)) {
2813 return Err(OuterReject::NotInt);
2814 }
2815 let ci = eval_affine(store_idx, consts, &lets).ok_or(OuterReject::NonAffineIndex)?;
2816 let ai = eval_affine(ai, consts, &lets).ok_or(OuterReject::NonAffineIndex)?;
2817 let bi = eval_affine(bi, consts, &lets).ok_or(OuterReject::NonAffineIndex)?;
2818 let li = counted_at(stmts, at, bound_i, body_i, consts).ok_or(OuterReject::NotCounted)?;
2819 let lj = counted_at(body_i, at_j, bound_j, body_j, consts).ok_or(OuterReject::NotCounted)?;
2820 let (iv_i, iv_j, n) = (li.iv, lj.iv, lj.trips);
2821 let only = |x: &Affine, pairs: &[(Symbol, i64)]| {
2822 x.coeff.len() == pairs.len() && pairs.iter().all(|(s, k)| x.coeff_of(*s) == *k)
2823 };
2824 if !(only(&ci, &[(iv_i, n), (iv_j, 1)]) && only(&ai, &[(iv_i, 1)]) && only(&bi, &[(iv_j, 1)])) {
2825 return Err(OuterReject::NotOuterLayout);
2826 }
2827 let kept = [iv_i, iv_j, a, b, c];
2828 let store = |s: &Stmt| matches!(s, Stmt::SetIndex { .. });
2829 let inner_ok = body_j
2830 .iter()
2831 .all(|s| is_unit_step(s, iv_j) || store(s) || is_body_let(s, &kept))
2832 && body_j.iter().filter(|s| store(s)).count() == 1;
2833 let j_init = |k: usize| {
2834 k < at_j
2835 && body_i[k..at_j]
2836 .iter()
2837 .all(|s| matches!(s, Stmt::Let { .. } | Stmt::Set { .. }))
2838 && matches!(&body_i[k], Stmt::Let { var, .. } | Stmt::Set { target: var, .. } if *var == iv_j)
2839 };
2840 let outer_ok = body_i.iter().enumerate().all(|(k, s)| {
2841 k == at_j || is_unit_step(s, iv_i) || j_init(k) || is_body_let(s, &kept)
2842 }) && body_i
2843 .iter()
2844 .filter(|s| matches!(s, Stmt::While { .. }))
2845 .count()
2846 == 1;
2847 if !(inner_ok && outer_ok) {
2848 return Err(OuterReject::ExtraWork);
2849 }
2850 Ok(OuterSpec {
2851 m: li.trips as usize,
2852 n: n as usize,
2853 a,
2854 b,
2855 c,
2856 })
2857}
2858
2859pub fn hoist_common_operand(term: ZipTerm) -> ZipTerm {
2863 match term {
2864 ZipTerm::Op(op, l, r) => ZipTerm::Op(op, Box::new(hoist_common_operand(*l)), Box::new(hoist_common_operand(*r))),
2865 ZipTerm::Abs(x) => ZipTerm::Abs(Box::new(hoist_common_operand(*x))),
2866 ZipTerm::Select(c, a, b) => {
2867 let (c, a, b) = (hoist_common_operand(*c), hoist_common_operand(*a), hoist_common_operand(*b));
2868 match (a, b) {
2869 (ZipTerm::Op(o1, l1, r1), ZipTerm::Op(o2, l2, r2))
2870 if o1 == o2 && matches!(o1, ZipOp::Add | ZipOp::Sub | ZipOp::Mul) && (l1 == l2 || r1 == r2) =>
2871 {
2872 let select = |x: ZipTerm, y: ZipTerm| hoist_common_operand(ZipTerm::Select(Box::new(c.clone()), Box::new(x), Box::new(y)));
2873 if l1 == l2 {
2874 ZipTerm::Op(o1, l1, Box::new(select(*r1, *r2)))
2875 } else {
2876 ZipTerm::Op(o1, Box::new(select(*l1, *l2)), r1)
2877 }
2878 }
2879 (a, b) => ZipTerm::Select(Box::new(c), Box::new(a), Box::new(b)),
2880 }
2881 }
2882 leaf => leaf,
2883 }
2884}
2885
2886#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2889pub enum Axis {
2890 Row,
2891 Col,
2892 RowCol,
2893}
2894
2895#[derive(Debug, Clone, PartialEq, Eq)]
2899pub struct ScatterSpec {
2900 pub rows: usize,
2901 pub cols: usize,
2902 pub sub: Symbol,
2904 pub sigma: Symbol,
2905 pub operands: Vec<(Symbol, Axis)>,
2908 pub term: ZipTerm,
2910}
2911
2912#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2914pub enum ScatterReject {
2915 NoLoop,
2917 NoRowLoop,
2919 NoRowStore,
2922 UnsupportedTerm,
2925 NotInt,
2927 NonAffineIndex,
2929 NotScatterLayout,
2931 NoRank1Update,
2933 NotCounted,
2935 ExtraWork,
2937 NotFromZero,
2939}
2940
2941pub fn recognize_scatter(stmts: &[Stmt], interner: &Interner) -> Result<ScatterSpec, ScatterReject> {
2969 let mut consts: HashMap<Symbol, i64> = HashMap::new();
2970 collect_consts_rec(stmts, &mut consts);
2971 let program = Program::of(stmts, interner);
2972 let spec = search_loops(stmts, ScatterReject::NoLoop, &|block, at, cond, body| {
2973 try_scatter_at(block, at, cond, body, &consts, &program)
2974 })?;
2975 if recognize_fill(stmts, spec.sigma) != Fill::Const(0) {
2976 return Err(ScatterReject::NotFromZero);
2977 }
2978 Ok(spec)
2979}
2980
2981struct Rows<'c> {
2984 r: Symbol,
2985 c: Symbol,
2986 cols: i64,
2987 consts: &'c HashMap<Symbol, i64>,
2988 lets: &'c HashMap<Symbol, Affine>,
2989 operands: Vec<(Symbol, Axis)>,
2990}
2991
2992impl<'c> Rows<'c> {
2993 fn read(&mut self, array: Symbol, index: &Expr) -> Result<usize, TermReject> {
2994 let at = eval_affine(index, self.consts, self.lets).ok_or(TermReject::NonAffine)?;
2995 let (cr, cc) = (at.coeff_of(self.r), at.coeff_of(self.c));
2996 let axis = match (at.coeff.len(), cr, cc) {
2997 (1, 1, 0) => Axis::Row,
2998 (1, 0, 1) => Axis::Col,
2999 (2, r, 1) if r == self.cols => Axis::RowCol,
3000 _ => return Err(TermReject::Layout),
3001 };
3002 match self.operands.iter().position(|(o, _)| *o == array) {
3003 Some(k) if self.operands[k].1 == axis => Ok(k),
3004 Some(_) => Err(TermReject::Layout),
3005 None => {
3006 self.operands.push((array, axis));
3007 Ok(self.operands.len() - 1)
3008 }
3009 }
3010 }
3011}
3012
3013impl From<TermReject> for ScatterReject {
3014 fn from(r: TermReject) -> ScatterReject {
3015 match r {
3016 TermReject::Unsupported => ScatterReject::UnsupportedTerm,
3017 TermReject::NonAffine => ScatterReject::NonAffineIndex,
3018 TermReject::Layout => ScatterReject::NotScatterLayout,
3019 }
3020 }
3021}
3022
3023fn row_store<'a>(
3026 s: &'a Stmt<'a>,
3027) -> Option<(Symbol, &'a Expr<'a>, Option<&'a Expr<'a>>, &'a Expr<'a>, Option<&'a Expr<'a>>)> {
3028 let store = |s: &'a Stmt<'a>| match s {
3029 Stmt::SetIndex { collection, index, value } => Some((as_ident(collection)?, *index, *value)),
3030 _ => None,
3031 };
3032 if let Some((sub, idx, value)) = store(s) {
3033 return Some((sub, idx, None, value, None));
3034 }
3035 let Stmt::If { cond, then_block, else_block: Some(else_block) } = s else { return None };
3036 let ([then], [other]) = (then_block, else_block) else { return None };
3037 let ((sub, idx, a), (sub2, _, b)) = (store(then)?, store(other)?);
3038 (sub == sub2).then_some((sub, idx, Some(*cond), a, Some(b)))
3039}
3040
3041fn try_scatter_at<'a>(
3042 stmts: &'a [Stmt<'a>],
3043 at: usize,
3044 cond: &'a Expr<'a>,
3045 body: &'a [Stmt<'a>],
3046 consts: &HashMap<Symbol, i64>,
3047 program: &Program<'a>,
3048) -> Result<ScatterSpec, ScatterReject> {
3049 let bound_r = loop_iv_bound(cond, consts).ok_or(ScatterReject::NoLoop)?;
3050 let loops: Vec<(usize, &Expr, &[Stmt])> = body
3051 .iter()
3052 .enumerate()
3053 .filter_map(|(k, s)| match s {
3054 Stmt::While { cond, body, .. } => Some((k, *cond, *body)),
3055 _ => None,
3056 })
3057 .collect();
3058 let (at_c, cond_c, body_c) = *loops.first().ok_or(ScatterReject::NoRowLoop)?;
3059 let bound_c = loop_iv_bound(cond_c, consts).ok_or(ScatterReject::NoRowLoop)?;
3060 let (pos, (sub, idx, guard, then, otherwise)) = body_c
3061 .iter()
3062 .enumerate()
3063 .find_map(|(k, s)| row_store(s).map(|w| (k, w)))
3064 .ok_or(ScatterReject::NoRowStore)?;
3065 let lr = counted_at(stmts, at, bound_r, body, consts).ok_or(ScatterReject::NotCounted)?;
3066 let lc = counted_at(body, at_c, bound_c, body_c, consts).ok_or(ScatterReject::NotCounted)?;
3067 let (lets, exprs) = body_lets(body[..at_c].iter().chain(body_c), consts);
3068 let at_sub = eval_affine(idx, consts, &lets).ok_or(ScatterReject::NonAffineIndex)?;
3069 if at_sub.coeff.len() != 1 || at_sub.coeff_of(lc.iv) != 1 {
3070 return Err(ScatterReject::NoRowStore);
3071 }
3072 let mut rows = Rows { r: lr.iv, c: lc.iv, cols: lc.trips, consts, lets: &lets, operands: Vec::new() };
3073 let mut terms = Terms::new(program, consts, &exprs, vec![lr.iv, lc.iv, sub]);
3074 let mut read = |a, i| rows.read(a, i);
3075 let term = match guard {
3076 None => terms.term(then, &mut read)?,
3077 Some(g) => {
3078 let g = terms.term(g, &mut read)?;
3079 let (a, b) = (terms.term(then, &mut read)?, terms.term(otherwise.expect("both arms"), &mut read)?);
3080 ZipTerm::Select(Box::new(g), Box::new(a), Box::new(b))
3081 }
3082 };
3083 let types = terms.types().ok_or(ScatterReject::UnsupportedTerm)?;
3084 match term.ty(&types) {
3085 None | Some(Ty::Bool) => return Err(ScatterReject::UnsupportedTerm),
3086 Some(Ty::Int) if types.iter().all(|t| *t == Ty::Int) => {}
3087 Some(_) => return Err(ScatterReject::NotInt),
3088 }
3089 let (at_i, cond_i, body_i) = *loops.get(1).ok_or(ScatterReject::NoRank1Update)?;
3090 let nest = outer_nest_at(body, at_i, cond_i, body_i, consts, program, true).map_err(|e| match e {
3091 OuterReject::NotCounted => ScatterReject::NotCounted,
3092 OuterReject::ExtraWork => ScatterReject::ExtraWork,
3093 _ => ScatterReject::NoRank1Update,
3094 })?;
3095 if (nest.a, nest.b) != (sub, sub) || (nest.m as i64, nest.n as i64) != (lc.trips, lc.trips) {
3096 return Err(ScatterReject::NoRank1Update);
3097 }
3098 let mut kept = vec![lr.iv, lc.iv, sub, nest.c];
3099 kept.extend(rows.operands.iter().map(|(o, _)| *o));
3100 let init_of = |block: &[Stmt], at_loop: usize, iv: Symbol, k: usize| {
3101 k < at_loop
3102 && block[k..at_loop].iter().all(|s| matches!(s, Stmt::Let { .. } | Stmt::Set { .. }))
3103 && matches!(&block[k], Stmt::Let { var, .. } | Stmt::Set { target: var, .. } if *var == iv)
3104 };
3105 let i_iv = loop_iv_bound(cond_i, consts).map(|(iv, _)| iv);
3106 let row_ok = body.iter().enumerate().all(|(k, s)| {
3107 k == at_c
3108 || k == at_i
3109 || is_unit_step(s, lr.iv)
3110 || init_of(body, at_c, lc.iv, k)
3111 || i_iv.map_or(false, |iv| init_of(body, at_i, iv, k))
3112 || is_body_let(s, &kept)
3113 }) && loops.len() == 2;
3114 let col_ok = body_c.iter().enumerate().all(|(k, s)| k == pos || is_unit_step(s, lc.iv) || is_body_let(s, &kept));
3115 if !(row_ok && col_ok) {
3116 return Err(ScatterReject::ExtraWork);
3117 }
3118 Ok(ScatterSpec {
3119 rows: lr.trips as usize,
3120 cols: lc.trips as usize,
3121 sub,
3122 sigma: nest.c,
3123 operands: rows.operands,
3124 term: hoist_common_operand(term),
3125 })
3126}
3127
3128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3130pub enum Ties {
3131 First,
3133 Last,
3135}
3136
3137#[derive(Debug, Clone, PartialEq, Eq)]
3143pub struct KmeansSpec {
3144 pub n: usize,
3145 pub k: usize,
3146 pub f: usize,
3147 pub iters: usize,
3148 pub points: Symbol,
3149 pub centroids: Symbol,
3150 pub distance: ZipTerm,
3152 pub ties: Ties,
3153}
3154
3155impl KmeansSpec {
3156 pub fn run(&self, points: &[i64], ints: (i128, i128)) -> Result<Vec<i64>, Stop> {
3160 let (n, k, f) = (self.n, self.k, self.f);
3161 if points.len() < n * f || n < k {
3162 return Err(Stop::Mistyped);
3163 }
3164 let within = |v: i128| if ints.0 <= v && v <= ints.1 { Ok(v as i64) } else { Err(Stop::IntRange) };
3165 for d in 0..f {
3166 within((0..n).map(|p| (points[p * f + d] as i128).abs()).sum())?;
3167 }
3168 within(n as i128)?;
3169 let dag = TermDag::of(&self.distance);
3170 let mut cts: Vec<i64> = points[..k * f].to_vec();
3171 for _ in 0..self.iters {
3172 let (mut sums, mut counts) = (vec![0i64; k * f], vec![0i64; k]);
3173 for p in 0..n {
3174 let (mut best, mut best_dist) = (0usize, 0i64);
3175 for c in 0..k {
3176 let (mut dist, mut magnitude) = (0i128, 0i128);
3177 for d in 0..f {
3178 let row = [Scalar::Int(points[p * f + d]), Scalar::Int(cts[c * f + d])];
3179 let Scalar::Int(v) = dag.value(&row, ints, f64::MAX)? else { return Err(Stop::Mistyped) };
3180 dist += v as i128;
3181 magnitude += (v as i128).abs();
3182 within(magnitude)?;
3183 }
3184 let dist = within(dist)?;
3185 let nearer = match self.ties {
3186 Ties::First => dist < best_dist,
3187 Ties::Last => dist <= best_dist,
3188 };
3189 if c == 0 || nearer {
3190 best = c;
3191 best_dist = dist;
3192 }
3193 }
3194 for d in 0..f {
3195 sums[best * f + d] += points[p * f + d];
3196 }
3197 counts[best] += 1;
3198 }
3199 for c in 0..k {
3200 for d in 0..f {
3201 cts[c * f + d] = if counts[c] == 0 { 0 } else { sums[c * f + d] / counts[c] };
3202 }
3203 }
3204 }
3205 Ok(cts)
3206 }
3207}
3208
3209#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3211pub enum KmeansReject {
3212 NoLoop,
3214 NoAssignment,
3217 NoDistance,
3219 UnsupportedTerm,
3222 NotInt,
3224 NonAffineIndex,
3226 NotRowLayout,
3228 NoArgmin,
3231 NoAccumulation,
3233 NoUpdate,
3235 NotZeroed,
3237 NotFromFirstPoints,
3239 NotCounted,
3241 ExtraWork,
3243}
3244
3245impl From<TermReject> for KmeansReject {
3246 fn from(r: TermReject) -> KmeansReject {
3247 match r {
3248 TermReject::Unsupported => KmeansReject::UnsupportedTerm,
3249 TermReject::NonAffine => KmeansReject::NonAffineIndex,
3250 TermReject::Layout => KmeansReject::NotRowLayout,
3251 }
3252 }
3253}
3254
3255pub fn recognize_kmeans(stmts: &[Stmt], interner: &Interner) -> Result<KmeansSpec, KmeansReject> {
3287 let mut consts: HashMap<Symbol, i64> = HashMap::new();
3288 collect_consts_rec(stmts, &mut consts);
3289 let program = Program::of(stmts, interner);
3290 let spec = search_loops(stmts, KmeansReject::NoLoop, &|block, at, cond, body| {
3291 try_kmeans_at(block, at, cond, body, &consts, &program)
3292 })?;
3293 match copied_prefix(stmts, spec.centroids, &consts) {
3294 Some((src, len)) if src == spec.points && len == (spec.k * spec.f) as i64 && spec.n >= spec.k => Ok(spec),
3295 _ => Err(KmeansReject::NotFromFirstPoints),
3296 }
3297}
3298
3299fn while_at<'a>(block: &'a [Stmt<'a>], at: usize) -> Option<(&'a Expr<'a>, &'a [Stmt<'a>])> {
3301 match &block[at] {
3302 Stmt::While { cond, body, .. } => Some((*cond, *body)),
3303 _ => None,
3304 }
3305}
3306
3307fn counted_while<'a>(
3310 block: &'a [Stmt<'a>],
3311 at: usize,
3312 consts: &HashMap<Symbol, i64>,
3313) -> Option<Result<(Symbol, i64, &'a [Stmt<'a>]), KmeansReject>> {
3314 let (cond, body) = while_at(block, at)?;
3315 let bound = loop_iv_bound(cond, consts)?;
3316 Some(counted_at(block, at, bound, body, consts).map(|c| (c.iv, c.trips, body)).ok_or(KmeansReject::NotCounted))
3317}
3318
3319fn copied_prefix(stmts: &[Stmt], dst: Symbol, consts: &HashMap<Symbol, i64>) -> Option<(Symbol, i64)> {
3322 let at = stmts.iter().position(|s| {
3323 matches!(s, Stmt::While { body, .. }
3324 if body.iter().any(|b| matches!(b, Stmt::Push { collection, .. } if as_ident(collection) == Some(dst))))
3325 })?;
3326 let (iv, trips, body) = counted_while(stmts, at, consts)?.ok()?;
3327 let value = body.iter().find_map(|b| match b {
3328 Stmt::Push { value, collection } if as_ident(collection) == Some(dst) => Some(*value),
3329 _ => None,
3330 })?;
3331 let src = indexed(value, &[(iv, 1)], consts, &HashMap::new())?;
3332 (entry_values(stmts, at, consts).get(&iv).copied().flatten() == Some(0)).then_some((src, trips))
3333}
3334
3335fn indexed(e: &Expr, pairs: &[(Symbol, i64)], consts: &HashMap<Symbol, i64>, lets: &HashMap<Symbol, Affine>) -> Option<Symbol> {
3337 let (coll, index) = as_index(e)?;
3338 let at = eval_affine(index, consts, lets)?;
3339 let shaped = at.cst == 1 && at.coeff.len() == pairs.len() && pairs.iter().all(|(s, k)| at.coeff_of(*s) == *k);
3340 shaped.then(|| as_ident(coll)).flatten()
3341}
3342
3343fn accumulation<'a>(s: &'a Stmt<'a>) -> Option<(Symbol, &'a Expr<'a>)> {
3345 let Stmt::Set { target, value } = s else { return None };
3346 let (l, r) = as_binop(value, BinaryOpKind::Add)?;
3347 match (as_ident(l) == Some(*target), as_ident(r) == Some(*target)) {
3348 (true, _) => Some((*target, r)),
3349 (_, true) => Some((*target, l)),
3350 _ => None,
3351 }
3352}
3353
3354fn indexed_accumulation<'a>(
3356 s: &'a Stmt<'a>,
3357 pairs: &[(Symbol, i64)],
3358 consts: &HashMap<Symbol, i64>,
3359) -> Option<(Symbol, &'a Expr<'a>)> {
3360 let Stmt::SetIndex { collection, index, value } = s else { return None };
3361 let a = as_ident(collection)?;
3362 let at = eval_affine(index, consts, &HashMap::new())?;
3363 if !(at.cst == 1 && at.coeff.len() == pairs.len() && pairs.iter().all(|(v, k)| at.coeff_of(*v) == *k)) {
3364 return None;
3365 }
3366 let (l, r) = as_binop(value, BinaryOpKind::Add)?;
3367 let own = |x: &Expr| indexed(x, pairs, consts, &HashMap::new()) == Some(a);
3368 if own(l) {
3369 Some((a, r))
3370 } else if own(r) {
3371 Some((a, l))
3372 } else {
3373 None
3374 }
3375}
3376
3377fn zeroing(block: &[Stmt], at: usize, consts: &HashMap<Symbol, i64>) -> Option<(Symbol, i64)> {
3379 let (iv, trips, body) = counted_while(block, at, consts)?.ok()?;
3380 let store = |s: &Stmt| match s {
3381 Stmt::SetIndex { collection, index, value: Expr::Literal(Literal::Number(0)) } => {
3382 let at = eval_affine(index, consts, &HashMap::new())?;
3383 (at.cst == 1 && at.coeff.len() == 1 && at.coeff_of(iv) == 1).then(|| as_ident(collection)).flatten()
3384 }
3385 _ => None,
3386 };
3387 let array = match body {
3388 [a, b] if is_unit_step(b, iv) => store(a)?,
3389 [a, b] if is_unit_step(a, iv) => store(b)?,
3390 _ => return None,
3391 };
3392 (entry_values(block, at, consts).get(&iv).copied().flatten() == Some(0)).then_some((array, trips))
3393}
3394
3395fn is_scaffold(block: &[Stmt], k: usize, kept: &[Symbol]) -> bool {
3398 let resets = |iv: Symbol| {
3399 matches!(&block[k], Stmt::Let { var, mutable: true, value: Expr::Literal(Literal::Number(_)), .. }
3400 | Stmt::Set { target: var, value: Expr::Literal(Literal::Number(_)) } if *var == iv)
3401 };
3402 is_body_let(&block[k], kept)
3403 || block[k + 1..].iter().any(|later| match later {
3404 Stmt::While { cond, .. } => as_binop_any(cond).and_then(|(l, _)| as_ident(l)).map_or(false, resets),
3405 _ => false,
3406 })
3407}
3408
3409fn starts_at(s: &Stmt, v: Symbol, zero: bool) -> bool {
3411 matches!(s, Stmt::Let { var, mutable: true, value: Expr::Literal(Literal::Number(n)), .. } if *var == v && (!zero || *n == 0))
3412}
3413
3414struct Assignment {
3417 at: usize,
3418 n: i64,
3419 k: i64,
3420 f: i64,
3421 kept: Vec<Symbol>,
3422 points: Symbol,
3423 centroids: Symbol,
3424 sums: Symbol,
3425 counts: Symbol,
3426 distance: ZipTerm,
3427 ties: Ties,
3428}
3429
3430impl Assignment {
3431 fn at<'a>(
3433 body: &'a [Stmt<'a>],
3434 at: usize,
3435 consts: &HashMap<Symbol, i64>,
3436 program: &Program<'a>,
3437 ) -> Option<Result<Assignment, KmeansReject>> {
3438 let (cond_p, body_p) = while_at(body, at)?;
3439 let (p, _) = loop_iv_bound(cond_p, consts)?;
3440 let at_c = body_p.iter().position(|s| matches!(s, Stmt::While { body, .. } if body.iter().any(|t| matches!(t, Stmt::While { .. }))))?;
3441 let (cond_c, body_c) = while_at(body_p, at_c)?;
3442 let (c, _) = loop_iv_bound(cond_c, consts)?;
3443 let at_d = body_c.iter().position(|s| matches!(s, Stmt::While { .. }))?;
3444 let (cond_d, body_d) = while_at(body_c, at_d)?;
3445 let (d, _) = loop_iv_bound(cond_d, consts)?;
3446 Some(Self::read(body, at, (p, body_p), (c, at_c, body_c), (d, at_d, body_d), consts, program))
3447 }
3448
3449 #[allow(clippy::too_many_arguments)]
3450 fn read<'a>(
3451 body: &'a [Stmt<'a>],
3452 at: usize,
3453 (p, body_p): (Symbol, &'a [Stmt<'a>]),
3454 (c, at_c, body_c): (Symbol, usize, &'a [Stmt<'a>]),
3455 (d, at_d, body_d): (Symbol, usize, &'a [Stmt<'a>]),
3456 consts: &HashMap<Symbol, i64>,
3457 program: &Program<'a>,
3458 ) -> Result<Assignment, KmeansReject> {
3459 let (pos_dist, (dist, term)) = body_d
3461 .iter()
3462 .enumerate()
3463 .find_map(|(k, s)| accumulation(s).filter(|(t, _)| *t != d).map(|a| (k, a)))
3464 .ok_or(KmeansReject::NoDistance)?;
3465 let (lets, exprs) = body_lets(body_p[..at_c].iter().chain(&body_c[..at_d]).chain(body_d), consts);
3466 let mut roles: [Option<Symbol>; 2] = [None, None];
3467 let mut width: Option<i64> = None;
3468 let mut read = |array: Symbol, index: &'a Expr<'a>| -> Result<usize, TermReject> {
3469 let at = eval_affine(index, consts, &lets).ok_or(TermReject::NonAffine)?;
3470 let role = [p, c].iter().position(|r| at.coeff_of(*r) != 0).ok_or(TermReject::Layout)?;
3471 let w = at.coeff_of([p, c][role]);
3472 if at.coeff.len() != 2 || at.coeff_of(d) != 1 || at.cst != 1 || width.map_or(false, |x| x != w) {
3473 return Err(TermReject::Layout);
3474 }
3475 width = Some(w);
3476 match roles[role] {
3477 Some(a) if a != array => Err(TermReject::Layout),
3478 _ => {
3479 roles[role] = Some(array);
3480 Ok(role)
3481 }
3482 }
3483 };
3484 let mut terms = Terms::new(program, consts, &exprs, vec![p, c, d, dist]);
3485 let distance = terms.term(term, &mut read)?;
3486 let ([Some(points), Some(centroids)], Some(f)) = (roles, width) else { return Err(KmeansReject::NotRowLayout) };
3487 let types = terms.types().ok_or(KmeansReject::NotRowLayout)?;
3488 match distance.ty(&types) {
3489 None | Some(Ty::Bool) => return Err(KmeansReject::UnsupportedTerm),
3490 Some(Ty::Int) if types == [Ty::Int, Ty::Int] => {}
3491 Some(_) => return Err(KmeansReject::NotInt),
3492 }
3493 if points == centroids {
3494 return Err(KmeansReject::NotRowLayout);
3495 }
3496 let (pos_if, best, best_dist, ties) = body_c
3498 .iter()
3499 .enumerate()
3500 .find_map(|(k, s)| argmin_update(s, c, dist).map(|(b, bd, t)| (k, b, bd, t)))
3501 .ok_or(KmeansReject::NoArgmin)?;
3502 let row_sum = |k: usize| -> Option<(Symbol, i64)> {
3504 let (e, trips, body_e) = counted_while(body_p, k, consts)?.ok()?;
3505 let [a, b] = body_e else { return None };
3506 let add = if is_unit_step(b, e) { a } else if is_unit_step(a, e) { b } else { return None };
3507 let (sums, x) = indexed_accumulation(add, &[(best, f), (e, 1)], consts)?;
3508 (indexed(x, &[(p, f), (e, 1)], consts, &HashMap::new()) == Some(points)).then_some((sums, trips))
3509 };
3510 let (at_sum, (sums, sum_trips)) = (0..body_p.len())
3511 .filter(|k| *k != at_c)
3512 .find_map(|k| row_sum(k).map(|r| (k, r)))
3513 .ok_or(KmeansReject::NoAccumulation)?;
3514 let counted = |s: &Stmt| {
3515 indexed_accumulation(s, &[(best, 1)], consts)
3516 .filter(|(_, x)| matches!(x, Expr::Literal(Literal::Number(1))))
3517 .map(|(a, _)| a)
3518 };
3519 let (at_count, counts) = body_p
3520 .iter()
3521 .enumerate()
3522 .find_map(|(k, s)| counted(s).map(|a| (k, a)))
3523 .ok_or(KmeansReject::NoAccumulation)?;
3524 if sum_trips != f || sums == counts || [sums, counts].contains(&points) || [sums, counts].contains(¢roids) {
3525 return Err(KmeansReject::NoAccumulation);
3526 }
3527 let (p, n, _) = counted_while(body, at, consts).expect("a loop over a counter")?;
3529 let (c, k, _) = counted_while(body_p, at_c, consts).expect("a loop over a counter")?;
3530 let (d, f_trips, _) = counted_while(body_c, at_d, consts).expect("a loop over a counter")?;
3531 if f_trips != f {
3532 return Err(KmeansReject::NotRowLayout);
3533 }
3534 let kept = vec![p, c, d, dist, best, best_dist, points, centroids, sums, counts];
3536 let point_ok = body_p.iter().enumerate().all(|(j, s)| {
3537 [at_c, at_sum, at_count].contains(&j)
3538 || is_unit_step(s, p)
3539 || starts_at(s, best, false)
3540 || starts_at(s, best_dist, false)
3541 || is_scaffold(body_p, j, &kept)
3542 }) && body_p.iter().any(|s| starts_at(s, best, false))
3543 && body_p.iter().any(|s| starts_at(s, best_dist, false));
3544 let centroid_ok = body_c.iter().enumerate().all(|(j, s)| {
3545 j == at_d || j == pos_if || is_unit_step(s, c) || starts_at(s, dist, true) || is_scaffold(body_c, j, &kept)
3546 }) && body_c[..at_d].iter().any(|s| starts_at(s, dist, true));
3547 let feature_ok =
3548 body_d.iter().enumerate().all(|(j, s)| j == pos_dist || is_unit_step(s, d) || is_body_let(s, &kept));
3549 if !(point_ok && centroid_ok && feature_ok) {
3550 return Err(KmeansReject::ExtraWork);
3551 }
3552 Ok(Assignment { at, n, k, f, kept, points, centroids, sums, counts, distance, ties })
3553 }
3554}
3555
3556fn argmin_update(s: &Stmt, c: Symbol, dist: Symbol) -> Option<(Symbol, Symbol, Ties)> {
3559 let Stmt::If { cond, then_block: [a, b], else_block: None } = s else { return None };
3560 let (l, r) = as_binop(cond, BinaryOpKind::Or)?;
3561 let first = |e: &Expr| {
3562 as_binop(e, BinaryOpKind::Eq)
3563 .map_or(false, |(x, z)| as_ident(x) == Some(c) && matches!(z, Expr::Literal(Literal::Number(0))))
3564 };
3565 let nearer = |e: &Expr| -> Option<(Symbol, Ties)> {
3566 let Expr::BinaryOp { op, left, right } = e else { return None };
3567 let ties = match op {
3568 BinaryOpKind::LtEq => Ties::Last,
3569 BinaryOpKind::Lt => Ties::First,
3570 _ => return None,
3571 };
3572 (as_ident(left) == Some(dist)).then_some((as_ident(right)?, ties))
3573 };
3574 let (best_dist, ties) = if first(l) { nearer(r)? } else if first(r) { nearer(l)? } else { return None };
3575 let set = |s: &Stmt| match s {
3576 Stmt::Set { target, value } => Some((*target, as_ident(value)?)),
3577 _ => None,
3578 };
3579 let (sa, sb) = (set(a)?, set(b)?);
3580 let best = [sa, sb].iter().find(|(_, v)| *v == c).map(|(t, _)| *t)?;
3581 ([sa, sb].contains(&(best_dist, dist)) && best != best_dist && best_dist != dist).then_some((best, best_dist, ties))
3582}
3583
3584fn centroid_update(
3588 block: &[Stmt],
3589 at: usize,
3590 a: &Assignment,
3591 consts: &HashMap<Symbol, i64>,
3592) -> Option<Result<i64, KmeansReject>> {
3593 let (_, body_j) = while_at(block, at)?;
3594 let at_e = body_j.iter().position(|s| matches!(s, Stmt::While { .. }))?;
3595 Some((|| {
3596 let (j, trips, body_j) = counted_while(block, at, consts).expect("a loop")?;
3597 let (e, f_trips, body_e) = counted_while(body_j, at_e, consts).ok_or(KmeansReject::NoUpdate)??;
3598 let (lets, exprs) = body_lets(&body_j[..at_e], consts);
3599 let count = |x: &Expr| indexed(through_lets(x, &exprs), &[(j, 1)], consts, &lets) == Some(a.counts);
3600 let feature = |x: &Expr| indexed(x, &[(j, a.f), (e, 1)], consts, &lets);
3601 let set_feature = |s: &Stmt| -> Option<bool> {
3602 let Stmt::SetIndex { collection, index, value } = s else { return None };
3603 let at = eval_affine(index, consts, &lets)?;
3604 let ok = as_ident(collection) == Some(a.centroids)
3605 && at.cst == 1
3606 && at.coeff.len() == 2
3607 && at.coeff_of(j) == a.f
3608 && at.coeff_of(e) == 1;
3609 ok.then(|| match value {
3610 Expr::Literal(Literal::Number(0)) => true,
3611 v => as_binop(v, BinaryOpKind::Divide).map_or(false, |(n, d)| feature(n) == Some(a.sums) && count(d)),
3612 })
3613 };
3614 let rule = |s: &Stmt| {
3615 let Stmt::If { cond, then_block: [t], else_block: Some([o]) } = s else { return false };
3616 let empty =
3617 as_binop(cond, BinaryOpKind::Eq).map_or(false, |(x, z)| count(x) && matches!(z, Expr::Literal(Literal::Number(0))));
3618 let zero = matches!(t, Stmt::SetIndex { value: Expr::Literal(Literal::Number(0)), .. }) && set_feature(t) == Some(true);
3619 let mean = !matches!(o, Stmt::SetIndex { value: Expr::Literal(_), .. }) && set_feature(o) == Some(true);
3620 empty && zero && mean
3621 };
3622 let pos = body_e.iter().position(rule).ok_or(KmeansReject::NoUpdate)?;
3623 if f_trips != a.f {
3624 return Err(KmeansReject::NotRowLayout);
3625 }
3626 let mut kept = a.kept.clone();
3627 kept.extend([j, e]);
3628 let only = body_e.iter().enumerate().all(|(k, s)| k == pos || is_unit_step(s, e))
3629 && body_j.iter().enumerate().all(|(k, s)| k == at_e || is_unit_step(s, j) || is_scaffold(body_j, k, &kept));
3630 if only {
3631 Ok(trips)
3632 } else {
3633 Err(KmeansReject::ExtraWork)
3634 }
3635 })())
3636}
3637
3638fn try_kmeans_at<'a>(
3639 stmts: &'a [Stmt<'a>],
3640 at: usize,
3641 cond: &'a Expr<'a>,
3642 body: &'a [Stmt<'a>],
3643 consts: &HashMap<Symbol, i64>,
3644 program: &Program<'a>,
3645) -> Result<KmeansSpec, KmeansReject> {
3646 let bound = loop_iv_bound(cond, consts).ok_or(KmeansReject::NoLoop)?;
3647 let a = (0..body.len())
3648 .find_map(|k| Assignment::at(body, k, consts, program))
3649 .ok_or(KmeansReject::NoAssignment)??;
3650 let (at_u, trips) = (0..body.len())
3651 .filter(|k| *k != a.at)
3652 .find_map(|k| centroid_update(body, k, &a, consts).map(|r| r.map(|t| (k, t))))
3653 .ok_or(KmeansReject::NoUpdate)??;
3654 let zeroed: Vec<(usize, (Symbol, i64))> = (0..a.at).filter_map(|k| zeroing(body, k, consts).map(|z| (k, z))).collect();
3655 let has = |arr: Symbol, len: i64| zeroed.iter().any(|(_, z)| *z == (arr, len));
3656 if trips != a.k || !(has(a.sums, a.k * a.f) && has(a.counts, a.k)) {
3657 return Err(KmeansReject::NotZeroed);
3658 }
3659 let epoch = counted_at(stmts, at, bound, body, consts).ok_or(KmeansReject::NotCounted)?;
3660 let mut kept = a.kept.clone();
3661 kept.push(epoch.iv);
3662 let epoch_ok = body.iter().enumerate().all(|(k, s)| {
3663 k == a.at
3664 || k == at_u
3665 || zeroed.iter().any(|(z, _)| *z == k)
3666 || is_unit_step(s, epoch.iv)
3667 || is_scaffold(body, k, &kept)
3668 });
3669 if !epoch_ok {
3670 return Err(KmeansReject::ExtraWork);
3671 }
3672 if [a.points, a.centroids, a.sums, a.counts].iter().any(|x| program.elem(*x) != Some(Ty::Int)) {
3673 return Err(KmeansReject::NotInt);
3674 }
3675 Ok(KmeansSpec {
3676 n: a.n as usize,
3677 k: a.k as usize,
3678 f: a.f as usize,
3679 iters: epoch.trips as usize,
3680 points: a.points,
3681 centroids: a.centroids,
3682 distance: a.distance,
3683 ties: a.ties,
3684 })
3685}