1use super::ast::{BinaryOp, Expr, Program, Statement, UnaryOp};
10
11#[cfg(not(feature = "std"))]
12use alloc::{
13 boxed::Box,
14 collections::{BTreeMap as HashMap, BTreeSet as HashSet},
15 format,
16 string::{String, ToString},
17 vec::Vec,
18};
19
20#[cfg(feature = "std")]
21use std::collections::{HashMap, HashSet};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum OptLevel {
26 None,
28 Basic,
30 Standard,
32 Aggressive,
34}
35
36pub struct Optimizer {
38 level: OptLevel,
39}
40
41impl Default for Optimizer {
42 fn default() -> Self {
43 Self::new(OptLevel::Standard)
44 }
45}
46
47impl Optimizer {
48 pub fn new(level: OptLevel) -> Self {
50 Self { level }
51 }
52
53 pub fn optimize_program(&self, mut program: Program) -> Program {
55 if self.level == OptLevel::None {
56 return program;
57 }
58
59 program.statements = program
60 .statements
61 .into_iter()
62 .map(|stmt| self.optimize_statement(stmt))
63 .collect();
64
65 if self.level == OptLevel::Aggressive {
68 program = self.eliminate_dead_code(program);
69 }
70
71 program
72 }
73
74 pub fn optimize_statement(&self, stmt: Statement) -> Statement {
76 match stmt {
77 Statement::VariableDecl { name, value } => Statement::VariableDecl {
78 name,
79 value: Box::new(self.optimize_expr(*value)),
80 },
81 Statement::FunctionDecl { name, params, body } => Statement::FunctionDecl {
82 name,
83 params,
84 body: Box::new(self.optimize_expr(*body)),
85 },
86 Statement::Return(expr) => Statement::Return(Box::new(self.optimize_expr(*expr))),
87 Statement::Expr(expr) => Statement::Expr(Box::new(self.optimize_expr(*expr))),
88 }
89 }
90
91 pub fn optimize_expr(&self, expr: Expr) -> Expr {
93 if self.level == OptLevel::None {
94 return expr;
95 }
96
97 let mut optimized = expr;
98
99 optimized = self.constant_fold(optimized);
101
102 if matches!(self.level, OptLevel::Standard | OptLevel::Aggressive) {
104 optimized = self.algebraic_simplify(optimized);
105 }
106
107 if self.level == OptLevel::Aggressive {
109 optimized = self.eliminate_common_subexpressions(optimized);
110 }
111
112 optimized
113 }
114
115 fn constant_fold(&self, expr: Expr) -> Expr {
117 match expr {
118 Expr::Binary {
119 left,
120 op,
121 right,
122 ty,
123 } => {
124 let left_opt = self.constant_fold(*left);
125 let right_opt = self.constant_fold(*right);
126
127 if let (Expr::Number(l), Expr::Number(r)) = (&left_opt, &right_opt) {
128 if let Some(result) = self.eval_const_binary(*l, op, *r) {
129 return Expr::Number(result);
130 }
131 }
132
133 Expr::Binary {
134 left: Box::new(left_opt),
135 op,
136 right: Box::new(right_opt),
137 ty,
138 }
139 }
140 Expr::Unary {
141 op,
142 expr: inner,
143 ty,
144 } => {
145 let inner_opt = self.constant_fold(*inner);
146
147 if let Expr::Number(n) = &inner_opt {
148 if let Some(result) = self.eval_const_unary(op, *n) {
149 return Expr::Number(result);
150 }
151 }
152
153 Expr::Unary {
154 op,
155 expr: Box::new(inner_opt),
156 ty,
157 }
158 }
159 Expr::Conditional {
160 condition,
161 then_expr,
162 else_expr,
163 ty,
164 } => {
165 let cond_opt = self.constant_fold(*condition);
166
167 if let Expr::Number(n) = &cond_opt {
169 if n.abs() > f64::EPSILON {
170 return self.constant_fold(*then_expr);
171 } else {
172 return self.constant_fold(*else_expr);
173 }
174 }
175
176 Expr::Conditional {
177 condition: Box::new(cond_opt),
178 then_expr: Box::new(self.constant_fold(*then_expr)),
179 else_expr: Box::new(self.constant_fold(*else_expr)),
180 ty,
181 }
182 }
183 Expr::Call { name, args, ty } => Expr::Call {
184 name,
185 args: args
186 .into_iter()
187 .map(|arg| self.constant_fold(arg))
188 .collect(),
189 ty,
190 },
191 Expr::Block {
192 statements,
193 result,
194 ty,
195 } => Expr::Block {
196 statements: statements
197 .into_iter()
198 .map(|stmt| self.optimize_statement(stmt))
199 .collect(),
200 result: result.map(|r| Box::new(self.constant_fold(*r))),
201 ty,
202 },
203 _ => expr,
204 }
205 }
206
207 fn eval_const_binary(&self, left: f64, op: BinaryOp, right: f64) -> Option<f64> {
209 let result = match op {
210 BinaryOp::Add => left + right,
211 BinaryOp::Subtract => left - right,
212 BinaryOp::Multiply => left * right,
213 BinaryOp::Divide => {
214 if right.abs() < f64::EPSILON {
215 return None;
216 }
217 left / right
218 }
219 BinaryOp::Modulo => left % right,
220 BinaryOp::Power => left.powf(right),
221 BinaryOp::Equal => {
222 if (left - right).abs() < f64::EPSILON {
223 1.0
224 } else {
225 0.0
226 }
227 }
228 BinaryOp::NotEqual => {
229 if (left - right).abs() >= f64::EPSILON {
230 1.0
231 } else {
232 0.0
233 }
234 }
235 BinaryOp::Less => {
236 if left < right {
237 1.0
238 } else {
239 0.0
240 }
241 }
242 BinaryOp::LessEqual => {
243 if left <= right {
244 1.0
245 } else {
246 0.0
247 }
248 }
249 BinaryOp::Greater => {
250 if left > right {
251 1.0
252 } else {
253 0.0
254 }
255 }
256 BinaryOp::GreaterEqual => {
257 if left >= right {
258 1.0
259 } else {
260 0.0
261 }
262 }
263 BinaryOp::And => {
264 if left != 0.0 && right != 0.0 {
265 1.0
266 } else {
267 0.0
268 }
269 }
270 BinaryOp::Or => {
271 if left != 0.0 || right != 0.0 {
272 1.0
273 } else {
274 0.0
275 }
276 }
277 };
278
279 Some(result)
280 }
281
282 fn eval_const_unary(&self, op: UnaryOp, operand: f64) -> Option<f64> {
284 let result = match op {
285 UnaryOp::Negate => -operand,
286 UnaryOp::Plus => operand,
287 UnaryOp::Not => {
288 if operand.abs() < f64::EPSILON {
289 1.0
290 } else {
291 0.0
292 }
293 }
294 };
295
296 Some(result)
297 }
298
299 fn algebraic_simplify(&self, expr: Expr) -> Expr {
301 match expr {
302 Expr::Binary {
303 left,
304 op,
305 right,
306 ty,
307 } => {
308 let left_opt = self.algebraic_simplify(*left);
309 let right_opt = self.algebraic_simplify(*right);
310
311 if op == BinaryOp::Add {
313 if let Expr::Number(n) = &right_opt {
314 if n.abs() < f64::EPSILON {
315 return left_opt;
316 }
317 }
318 if let Expr::Number(n) = &left_opt {
319 if n.abs() < f64::EPSILON {
320 return right_opt;
321 }
322 }
323 }
324
325 if op == BinaryOp::Subtract {
327 if let Expr::Number(n) = &right_opt {
328 if n.abs() < f64::EPSILON {
329 return left_opt;
330 }
331 }
332 }
333
334 if op == BinaryOp::Multiply {
343 if let Expr::Number(n) = &right_opt {
344 if (n - 1.0).abs() < f64::EPSILON {
345 return left_opt;
346 }
347 }
348 if let Expr::Number(n) = &left_opt {
349 if (n - 1.0).abs() < f64::EPSILON {
350 return right_opt;
351 }
352 }
353 }
354
355 if op == BinaryOp::Divide {
357 if let Expr::Number(n) = &right_opt {
358 if (n - 1.0).abs() < f64::EPSILON {
359 return left_opt;
360 }
361 }
362 }
363
364 if op == BinaryOp::Power {
366 if let Expr::Number(n) = &right_opt {
367 if n.abs() < f64::EPSILON {
368 return Expr::Number(1.0);
369 }
370 }
371 }
372
373 if op == BinaryOp::Power {
375 if let Expr::Number(n) = &right_opt {
376 if (n - 1.0).abs() < f64::EPSILON {
377 return left_opt;
378 }
379 }
380 }
381
382 Expr::Binary {
383 left: Box::new(left_opt),
384 op,
385 right: Box::new(right_opt),
386 ty,
387 }
388 }
389 Expr::Unary {
390 op,
391 expr: inner,
392 ty,
393 } => {
394 let inner_opt = self.algebraic_simplify(*inner);
395
396 if op == UnaryOp::Negate {
398 if let Expr::Unary {
399 op: UnaryOp::Negate,
400 expr: double_neg,
401 ..
402 } = &inner_opt
403 {
404 return *double_neg.clone();
405 }
406 }
407
408 if op == UnaryOp::Plus {
410 return inner_opt;
411 }
412
413 Expr::Unary {
414 op,
415 expr: Box::new(inner_opt),
416 ty,
417 }
418 }
419 Expr::Conditional {
420 condition,
421 then_expr,
422 else_expr,
423 ty,
424 } => Expr::Conditional {
425 condition: Box::new(self.algebraic_simplify(*condition)),
426 then_expr: Box::new(self.algebraic_simplify(*then_expr)),
427 else_expr: Box::new(self.algebraic_simplify(*else_expr)),
428 ty,
429 },
430 Expr::Call { name, args, ty } => Expr::Call {
431 name,
432 args: args
433 .into_iter()
434 .map(|arg| self.algebraic_simplify(arg))
435 .collect(),
436 ty,
437 },
438 Expr::Block {
439 statements,
440 result,
441 ty,
442 } => Expr::Block {
443 statements: statements
444 .into_iter()
445 .map(|stmt| self.optimize_statement(stmt))
446 .collect(),
447 result: result.map(|r| Box::new(self.algebraic_simplify(*r))),
448 ty,
449 },
450 _ => expr,
451 }
452 }
453
454 fn eliminate_common_subexpressions(&self, expr: Expr) -> Expr {
467 let mut counts: HashMap<String, usize> = HashMap::new();
469 let mut reprs: HashMap<String, Expr> = HashMap::new();
470 Self::collect_cse_candidates(&expr, &mut counts, &mut reprs);
471
472 let mut candidates: Vec<(String, Expr)> = counts
474 .iter()
475 .filter(|&(_, &c)| c >= 2)
476 .filter_map(|(k, _)| reprs.get(k).map(|e| (k.clone(), e.clone())))
477 .collect();
478
479 if candidates.is_empty() {
480 return expr;
481 }
482
483 candidates.sort_by(|(ka, ea), (kb, eb)| {
487 Self::expr_size(ea)
488 .cmp(&Self::expr_size(eb))
489 .then_with(|| ka.cmp(kb))
490 });
491
492 let mut name_map: HashMap<String, String> = HashMap::new();
494 for (i, (key, _)) in candidates.iter().enumerate() {
495 name_map.insert(key.clone(), format!("__cse_{i}"));
496 }
497
498 let mut statements: Vec<Statement> = Vec::with_capacity(candidates.len());
502 for (key, repr) in &candidates {
503 let name = name_map.get(key).cloned().unwrap_or_default();
505 let definition = Self::rewrite_children_with_cse(repr, &name_map);
506 statements.push(Statement::VariableDecl {
507 name,
508 value: Box::new(definition),
509 });
510 }
511
512 let ty = expr.get_type();
514 let body = Self::rewrite_with_cse(&expr, &name_map);
515
516 Expr::Block {
517 statements,
518 result: Some(Box::new(body)),
519 ty,
520 }
521 }
522
523 fn collect_cse_candidates(
526 expr: &Expr,
527 counts: &mut HashMap<String, usize>,
528 reprs: &mut HashMap<String, Expr>,
529 ) {
530 if Self::is_cse_target(expr) {
531 let key = Self::cse_key(expr);
532 *counts.entry(key.clone()).or_insert(0) += 1;
533 reprs.entry(key).or_insert_with(|| expr.clone());
534 }
535
536 match expr {
537 Expr::Binary { left, right, .. } => {
538 Self::collect_cse_candidates(left, counts, reprs);
539 Self::collect_cse_candidates(right, counts, reprs);
540 }
541 Expr::Unary { expr: inner, .. } => {
542 Self::collect_cse_candidates(inner, counts, reprs);
543 }
544 Expr::Call { args, .. } => {
545 for arg in args {
546 Self::collect_cse_candidates(arg, counts, reprs);
547 }
548 }
549 Expr::Conditional {
550 condition,
551 then_expr,
552 else_expr,
553 ..
554 } => {
555 Self::collect_cse_candidates(condition, counts, reprs);
556 Self::collect_cse_candidates(then_expr, counts, reprs);
557 Self::collect_cse_candidates(else_expr, counts, reprs);
558 }
559 Expr::Block {
560 statements, result, ..
561 } => {
562 for stmt in statements {
563 Self::collect_cse_candidates_stmt(stmt, counts, reprs);
564 }
565 if let Some(result) = result {
566 Self::collect_cse_candidates(result, counts, reprs);
567 }
568 }
569 Expr::ForLoop {
570 start, end, body, ..
571 } => {
572 Self::collect_cse_candidates(start, counts, reprs);
573 Self::collect_cse_candidates(end, counts, reprs);
574 Self::collect_cse_candidates(body, counts, reprs);
575 }
576 Expr::Number(_) | Expr::Band(_) | Expr::Variable(_) => {}
577 }
578 }
579
580 fn collect_cse_candidates_stmt(
581 stmt: &Statement,
582 counts: &mut HashMap<String, usize>,
583 reprs: &mut HashMap<String, Expr>,
584 ) {
585 match stmt {
586 Statement::VariableDecl { value, .. } => {
587 Self::collect_cse_candidates(value, counts, reprs);
588 }
589 Statement::FunctionDecl { body, .. } => {
590 Self::collect_cse_candidates(body, counts, reprs);
591 }
592 Statement::Return(expr) | Statement::Expr(expr) => {
593 Self::collect_cse_candidates(expr, counts, reprs);
594 }
595 }
596 }
597
598 fn is_cse_target(expr: &Expr) -> bool {
602 matches!(
603 expr,
604 Expr::Binary { .. } | Expr::Unary { .. } | Expr::Call { .. } | Expr::Conditional { .. }
605 ) && Self::is_hoistable(expr)
606 }
607
608 fn is_hoistable(expr: &Expr) -> bool {
610 match expr {
611 Expr::Number(_) | Expr::Band(_) => true,
612 Expr::Variable(_) | Expr::Block { .. } | Expr::ForLoop { .. } => false,
613 Expr::Binary { left, right, .. } => {
614 Self::is_hoistable(left) && Self::is_hoistable(right)
615 }
616 Expr::Unary { expr: inner, .. } => Self::is_hoistable(inner),
617 Expr::Call { args, .. } => args.iter().all(Self::is_hoistable),
618 Expr::Conditional {
619 condition,
620 then_expr,
621 else_expr,
622 ..
623 } => {
624 Self::is_hoistable(condition)
625 && Self::is_hoistable(then_expr)
626 && Self::is_hoistable(else_expr)
627 }
628 }
629 }
630
631 fn cse_key(expr: &Expr) -> String {
633 format!("{expr:?}")
634 }
635
636 fn expr_size(expr: &Expr) -> usize {
638 1 + match expr {
639 Expr::Number(_) | Expr::Band(_) | Expr::Variable(_) => 0,
640 Expr::Binary { left, right, .. } => Self::expr_size(left) + Self::expr_size(right),
641 Expr::Unary { expr: inner, .. } => Self::expr_size(inner),
642 Expr::Call { args, .. } => args.iter().map(Self::expr_size).sum(),
643 Expr::Conditional {
644 condition,
645 then_expr,
646 else_expr,
647 ..
648 } => {
649 Self::expr_size(condition) + Self::expr_size(then_expr) + Self::expr_size(else_expr)
650 }
651 Expr::Block {
652 statements, result, ..
653 } => {
654 statements.iter().map(Self::stmt_size).sum::<usize>()
655 + result.as_ref().map_or(0, |r| Self::expr_size(r))
656 }
657 Expr::ForLoop {
658 start, end, body, ..
659 } => Self::expr_size(start) + Self::expr_size(end) + Self::expr_size(body),
660 }
661 }
662
663 fn stmt_size(stmt: &Statement) -> usize {
664 match stmt {
665 Statement::VariableDecl { value, .. } => Self::expr_size(value),
666 Statement::FunctionDecl { body, .. } => Self::expr_size(body),
667 Statement::Return(expr) | Statement::Expr(expr) => Self::expr_size(expr),
668 }
669 }
670
671 fn rewrite_with_cse(expr: &Expr, name_map: &HashMap<String, String>) -> Expr {
674 let key = Self::cse_key(expr);
675 if let Some(name) = name_map.get(&key) {
676 return Expr::Variable(name.clone());
677 }
678 Self::rewrite_children_with_cse(expr, name_map)
679 }
680
681 fn rewrite_children_with_cse(expr: &Expr, name_map: &HashMap<String, String>) -> Expr {
684 match expr {
685 Expr::Binary {
686 left,
687 op,
688 right,
689 ty,
690 } => Expr::Binary {
691 left: Box::new(Self::rewrite_with_cse(left, name_map)),
692 op: *op,
693 right: Box::new(Self::rewrite_with_cse(right, name_map)),
694 ty: *ty,
695 },
696 Expr::Unary {
697 op,
698 expr: inner,
699 ty,
700 } => Expr::Unary {
701 op: *op,
702 expr: Box::new(Self::rewrite_with_cse(inner, name_map)),
703 ty: *ty,
704 },
705 Expr::Call { name, args, ty } => Expr::Call {
706 name: name.clone(),
707 args: args
708 .iter()
709 .map(|a| Self::rewrite_with_cse(a, name_map))
710 .collect(),
711 ty: *ty,
712 },
713 Expr::Conditional {
714 condition,
715 then_expr,
716 else_expr,
717 ty,
718 } => Expr::Conditional {
719 condition: Box::new(Self::rewrite_with_cse(condition, name_map)),
720 then_expr: Box::new(Self::rewrite_with_cse(then_expr, name_map)),
721 else_expr: Box::new(Self::rewrite_with_cse(else_expr, name_map)),
722 ty: *ty,
723 },
724 other => other.clone(),
728 }
729 }
730
731 fn eliminate_dead_code(&self, mut program: Program) -> Program {
735 let mut decl_refs: HashMap<String, Vec<String>> = HashMap::new();
737 for stmt in &program.statements {
738 match stmt {
739 Statement::VariableDecl { name, value } => {
740 let refs = Self::referenced_names(value);
741 decl_refs.entry(name.clone()).or_default().extend(refs);
742 }
743 Statement::FunctionDecl { name, params, body } => {
744 let mut refs = Self::referenced_names(body);
745 refs.retain(|n| !params.contains(n));
747 decl_refs.entry(name.clone()).or_default().extend(refs);
748 }
749 _ => {}
750 }
751 }
752
753 let mut live: HashSet<String> = HashSet::new();
755 let mut worklist: Vec<String> = Vec::new();
756 for stmt in &program.statements {
757 if let Statement::Return(expr) | Statement::Expr(expr) = stmt {
758 for name in Self::referenced_names(expr) {
759 if live.insert(name.clone()) {
760 worklist.push(name);
761 }
762 }
763 }
764 }
765
766 while let Some(name) = worklist.pop() {
768 if let Some(refs) = decl_refs.get(&name).cloned() {
769 for r in refs {
770 if live.insert(r.clone()) {
771 worklist.push(r);
772 }
773 }
774 }
775 }
776
777 program.statements.retain(|stmt| match stmt {
779 Statement::VariableDecl { name, .. } | Statement::FunctionDecl { name, .. } => {
780 live.contains(name)
781 }
782 Statement::Return(_) | Statement::Expr(_) => true,
783 });
784
785 program
786 }
787
788 fn referenced_names(expr: &Expr) -> Vec<String> {
790 let mut out = Vec::new();
791 Self::collect_referenced_names(expr, &mut out);
792 out
793 }
794
795 fn collect_referenced_names(expr: &Expr, out: &mut Vec<String>) {
796 match expr {
797 Expr::Variable(name) => out.push(name.clone()),
798 Expr::Number(_) | Expr::Band(_) => {}
799 Expr::Binary { left, right, .. } => {
800 Self::collect_referenced_names(left, out);
801 Self::collect_referenced_names(right, out);
802 }
803 Expr::Unary { expr: inner, .. } => Self::collect_referenced_names(inner, out),
804 Expr::Call { name, args, .. } => {
805 out.push(name.clone());
806 for arg in args {
807 Self::collect_referenced_names(arg, out);
808 }
809 }
810 Expr::Conditional {
811 condition,
812 then_expr,
813 else_expr,
814 ..
815 } => {
816 Self::collect_referenced_names(condition, out);
817 Self::collect_referenced_names(then_expr, out);
818 Self::collect_referenced_names(else_expr, out);
819 }
820 Expr::Block {
821 statements, result, ..
822 } => {
823 for stmt in statements {
824 Self::collect_referenced_names_stmt(stmt, out);
825 }
826 if let Some(result) = result {
827 Self::collect_referenced_names(result, out);
828 }
829 }
830 Expr::ForLoop {
831 start, end, body, ..
832 } => {
833 Self::collect_referenced_names(start, out);
834 Self::collect_referenced_names(end, out);
835 Self::collect_referenced_names(body, out);
836 }
837 }
838 }
839
840 fn collect_referenced_names_stmt(stmt: &Statement, out: &mut Vec<String>) {
841 match stmt {
842 Statement::VariableDecl { value, .. } => Self::collect_referenced_names(value, out),
843 Statement::FunctionDecl { body, .. } => Self::collect_referenced_names(body, out),
844 Statement::Return(expr) | Statement::Expr(expr) => {
845 Self::collect_referenced_names(expr, out)
846 }
847 }
848 }
849}
850
851#[cfg(test)]
852#[allow(clippy::panic)]
853mod tests {
854 use super::*;
855 use crate::dsl::Type;
856
857 #[test]
858 fn test_constant_fold_add() {
859 let expr = Expr::Binary {
860 left: Box::new(Expr::Number(2.0)),
861 op: BinaryOp::Add,
862 right: Box::new(Expr::Number(3.0)),
863 ty: Type::Number,
864 };
865
866 let opt = Optimizer::new(OptLevel::Basic);
867 let result = opt.optimize_expr(expr);
868
869 assert!(matches!(result, Expr::Number(n) if (n - 5.0).abs() < 1e-10));
870 }
871
872 #[test]
873 fn test_constant_fold_nested() {
874 let expr = Expr::Binary {
875 left: Box::new(Expr::Binary {
876 left: Box::new(Expr::Number(2.0)),
877 op: BinaryOp::Multiply,
878 right: Box::new(Expr::Number(3.0)),
879 ty: Type::Number,
880 }),
881 op: BinaryOp::Add,
882 right: Box::new(Expr::Number(4.0)),
883 ty: Type::Number,
884 };
885
886 let opt = Optimizer::new(OptLevel::Basic);
887 let result = opt.optimize_expr(expr);
888
889 assert!(matches!(result, Expr::Number(n) if (n - 10.0).abs() < 1e-10));
890 }
891
892 #[test]
893 fn test_algebraic_simplify_add_zero() {
894 let expr = Expr::Binary {
895 left: Box::new(Expr::Band(1)),
896 op: BinaryOp::Add,
897 right: Box::new(Expr::Number(0.0)),
898 ty: Type::Raster,
899 };
900
901 let opt = Optimizer::new(OptLevel::Standard);
902 let result = opt.optimize_expr(expr);
903
904 assert!(matches!(result, Expr::Band(1)));
905 }
906
907 #[test]
908 fn test_algebraic_simplify_mul_one() {
909 let expr = Expr::Binary {
910 left: Box::new(Expr::Band(1)),
911 op: BinaryOp::Multiply,
912 right: Box::new(Expr::Number(1.0)),
913 ty: Type::Raster,
914 };
915
916 let opt = Optimizer::new(OptLevel::Standard);
917 let result = opt.optimize_expr(expr);
918
919 assert!(matches!(result, Expr::Band(1)));
920 }
921
922 #[test]
923 fn test_algebraic_simplify_mul_zero_not_folded() {
924 let expr = Expr::Binary {
929 left: Box::new(Expr::Band(1)),
930 op: BinaryOp::Multiply,
931 right: Box::new(Expr::Number(0.0)),
932 ty: Type::Raster,
933 };
934
935 let opt = Optimizer::new(OptLevel::Standard);
936 let result = opt.optimize_expr(expr);
937
938 assert!(matches!(
940 result,
941 Expr::Binary {
942 op: BinaryOp::Multiply,
943 ..
944 }
945 ));
946 assert!(!matches!(result, Expr::Number(_)));
947
948 let expr_commuted = Expr::Binary {
950 left: Box::new(Expr::Number(0.0)),
951 op: BinaryOp::Multiply,
952 right: Box::new(Expr::Band(1)),
953 ty: Type::Raster,
954 };
955 let result_commuted = opt.optimize_expr(expr_commuted);
956 assert!(!matches!(result_commuted, Expr::Number(_)));
957 }
958
959 #[test]
960 fn test_algebraic_simplify_mul_zero_preserves_nan_semantics() {
961 use crate::dsl::RasterDsl;
968 use oxigeo_core::buffer::RasterBuffer;
969 use oxigeo_core::types::RasterDataType;
970
971 let mut b1 = RasterBuffer::zeros(1, 1, RasterDataType::Float32);
972 let mut b2 = RasterBuffer::zeros(1, 1, RasterDataType::Float32);
973 assert!(b1.set_pixel(0, 0, 1.0).is_ok());
975 assert!(b2.set_pixel(0, 0, 0.0).is_ok());
976
977 let dsl = RasterDsl::new();
978 let result = dsl
979 .execute("(B1 / B2) * 0", &[b1, b2])
980 .expect("DSL expression should execute successfully");
981
982 let pixel = result
983 .get_pixel(0, 0)
984 .expect("pixel (0, 0) should be readable");
985
986 assert!(
987 pixel.is_nan(),
988 "expected NoData (NaN) to survive `* 0`, got {pixel} instead"
989 );
990 }
991
992 #[test]
993 fn test_double_negation() {
994 let expr = Expr::Unary {
995 op: UnaryOp::Negate,
996 expr: Box::new(Expr::Unary {
997 op: UnaryOp::Negate,
998 expr: Box::new(Expr::Band(1)),
999 ty: Type::Raster,
1000 }),
1001 ty: Type::Raster,
1002 };
1003
1004 let opt = Optimizer::new(OptLevel::Standard);
1005 let result = opt.optimize_expr(expr);
1006
1007 assert!(matches!(result, Expr::Band(1)));
1008 }
1009
1010 #[test]
1011 fn test_unary_plus() {
1012 let expr = Expr::Unary {
1013 op: UnaryOp::Plus,
1014 expr: Box::new(Expr::Band(1)),
1015 ty: Type::Raster,
1016 };
1017
1018 let opt = Optimizer::new(OptLevel::Standard);
1019 let result = opt.optimize_expr(expr);
1020
1021 assert!(matches!(result, Expr::Band(1)));
1022 }
1023
1024 fn add_bands(a: usize, b: usize) -> Expr {
1026 Expr::Binary {
1027 left: Box::new(Expr::Band(a)),
1028 op: BinaryOp::Add,
1029 right: Box::new(Expr::Band(b)),
1030 ty: Type::Raster,
1031 }
1032 }
1033
1034 #[test]
1035 fn test_cse_hoists_repeated_subexpression() {
1036 let expr = Expr::Binary {
1038 left: Box::new(add_bands(1, 2)),
1039 op: BinaryOp::Multiply,
1040 right: Box::new(add_bands(1, 2)),
1041 ty: Type::Raster,
1042 };
1043
1044 let opt = Optimizer::new(OptLevel::Aggressive);
1045 let result = opt.optimize_expr(expr);
1046
1047 let Expr::Block {
1049 statements, result, ..
1050 } = result
1051 else {
1052 panic!("expected CSE to produce a Block, got {result:?}");
1053 };
1054 assert_eq!(statements.len(), 1, "one common subexpression expected");
1055 let Statement::VariableDecl { name, value } = &statements[0] else {
1056 panic!("expected a VariableDecl binding");
1057 };
1058 assert!(matches!(
1060 **value,
1061 Expr::Binary {
1062 op: BinaryOp::Add,
1063 ..
1064 }
1065 ));
1066 let body = result.expect("block must have a result");
1068 let Expr::Binary { left, right, .. } = *body else {
1069 panic!("expected a Binary body");
1070 };
1071 assert!(matches!(*left, Expr::Variable(ref n) if n == name));
1072 assert!(matches!(*right, Expr::Variable(ref n) if n == name));
1073 }
1074
1075 #[test]
1076 fn test_cse_no_hoist_when_unique() {
1077 let expr = add_bands(1, 2);
1079 let opt = Optimizer::new(OptLevel::Aggressive);
1080 let result = opt.optimize_expr(expr);
1081 assert!(
1082 matches!(result, Expr::Binary { .. }),
1083 "unique expression must not be wrapped in a CSE block"
1084 );
1085 }
1086
1087 #[test]
1088 fn test_cse_nested_common_subexpressions() {
1089 let times_b3 = |()| Expr::Binary {
1093 left: Box::new(add_bands(1, 2)),
1094 op: BinaryOp::Multiply,
1095 right: Box::new(Expr::Band(3)),
1096 ty: Type::Raster,
1097 };
1098 let expr = Expr::Binary {
1099 left: Box::new(times_b3(())),
1100 op: BinaryOp::Add,
1101 right: Box::new(times_b3(())),
1102 ty: Type::Raster,
1103 };
1104
1105 let opt = Optimizer::new(OptLevel::Aggressive);
1106 let result = opt.optimize_expr(expr);
1107
1108 let Expr::Block { statements, .. } = result else {
1109 panic!("expected a CSE block");
1110 };
1111 assert_eq!(statements.len(), 2, "inner and outer CSE expected");
1112 let Statement::VariableDecl {
1114 name: inner_name,
1115 value: inner_value,
1116 } = &statements[0]
1117 else {
1118 panic!("expected inner binding");
1119 };
1120 assert!(matches!(
1121 **inner_value,
1122 Expr::Binary {
1123 op: BinaryOp::Add,
1124 ..
1125 }
1126 ));
1127 let Statement::VariableDecl {
1130 value: outer_value, ..
1131 } = &statements[1]
1132 else {
1133 panic!("expected outer binding");
1134 };
1135 let Expr::Binary { left, .. } = &**outer_value else {
1136 panic!("expected outer binding to be a multiply");
1137 };
1138 assert!(
1139 matches!(**left, Expr::Variable(ref n) if n == inner_name),
1140 "outer binding must reference the inner CSE variable"
1141 );
1142 }
1143
1144 #[test]
1145 fn test_cse_preserves_result_value() {
1146 use crate::dsl::RasterDsl;
1147 use oxigeo_core::buffer::RasterBuffer;
1148 use oxigeo_core::types::RasterDataType;
1149
1150 let mut b1 = RasterBuffer::zeros(2, 2, RasterDataType::Float32);
1151 let mut b2 = RasterBuffer::zeros(2, 2, RasterDataType::Float32);
1152 for y in 0..2 {
1153 for x in 0..2 {
1154 assert!(b1.set_pixel(x, y, 3.0).is_ok());
1155 assert!(b2.set_pixel(x, y, 1.0).is_ok());
1156 }
1157 }
1158
1159 let expr = "(B1 - B2) / (B1 + B2)";
1162
1163 let mut plain = RasterDsl::new();
1164 plain.set_opt_level(OptLevel::None);
1165 let mut aggressive = RasterDsl::new();
1166 aggressive.set_opt_level(OptLevel::Aggressive);
1167
1168 let r_plain = plain
1169 .execute(expr, &[b1.clone(), b2.clone()])
1170 .expect("plain execution should succeed");
1171 let r_cse = aggressive
1172 .execute(expr, &[b1, b2])
1173 .expect("aggressive execution should succeed");
1174
1175 for y in 0..2 {
1176 for x in 0..2 {
1177 let a = r_plain.get_pixel(x, y).expect("pixel readable");
1178 let b = r_cse.get_pixel(x, y).expect("pixel readable");
1179 assert!((a - b).abs() < 1e-6, "CSE changed the value: {a} vs {b}");
1180 assert!((a - 0.5).abs() < 1e-6);
1181 }
1182 }
1183 }
1184
1185 #[test]
1186 fn test_dce_removes_unused_declaration() {
1187 let program = Program {
1191 statements: vec![
1192 Statement::VariableDecl {
1193 name: "used".to_string(),
1194 value: Box::new(add_bands(1, 2)),
1195 },
1196 Statement::VariableDecl {
1197 name: "dead".to_string(),
1198 value: Box::new(Expr::Binary {
1199 left: Box::new(Expr::Band(1)),
1200 op: BinaryOp::Multiply,
1201 right: Box::new(Expr::Band(2)),
1202 ty: Type::Raster,
1203 }),
1204 },
1205 Statement::Expr(Box::new(Expr::Variable("used".to_string()))),
1206 ],
1207 };
1208
1209 let opt = Optimizer::new(OptLevel::Aggressive);
1210 let result = opt.optimize_program(program);
1211
1212 assert_eq!(result.statements.len(), 2);
1214 assert!(matches!(
1215 &result.statements[0],
1216 Statement::VariableDecl { name, .. } if name == "used"
1217 ));
1218 assert!(matches!(&result.statements[1], Statement::Expr(_)));
1219 }
1220
1221 #[test]
1222 fn test_dce_keeps_transitively_used_declaration() {
1223 let program = Program {
1227 statements: vec![
1228 Statement::VariableDecl {
1229 name: "a".to_string(),
1230 value: Box::new(add_bands(1, 2)),
1231 },
1232 Statement::VariableDecl {
1233 name: "b".to_string(),
1234 value: Box::new(Expr::Binary {
1235 left: Box::new(Expr::Variable("a".to_string())),
1236 op: BinaryOp::Multiply,
1237 right: Box::new(Expr::Band(3)),
1238 ty: Type::Raster,
1239 }),
1240 },
1241 Statement::Expr(Box::new(Expr::Variable("b".to_string()))),
1242 ],
1243 };
1244
1245 let opt = Optimizer::new(OptLevel::Aggressive);
1246 let result = opt.optimize_program(program);
1247
1248 assert_eq!(result.statements.len(), 3);
1250 }
1251
1252 #[test]
1253 fn test_dce_not_applied_below_aggressive() {
1254 let program = Program {
1255 statements: vec![
1256 Statement::VariableDecl {
1257 name: "dead".to_string(),
1258 value: Box::new(add_bands(1, 2)),
1259 },
1260 Statement::Expr(Box::new(Expr::Band(1))),
1261 ],
1262 };
1263
1264 let opt = Optimizer::new(OptLevel::Standard);
1265 let result = opt.optimize_program(program);
1266
1267 assert_eq!(result.statements.len(), 2);
1269 }
1270}