1use nxpu_ir::{Arena, BinaryOp, Expression, Handle, Literal, MathFunction, Module, UnaryOp};
7
8use crate::Pass;
9
10#[derive(Debug)]
12pub struct ConstantFolding;
13
14impl Pass for ConstantFolding {
15 fn name(&self) -> &str {
16 "const-fold"
17 }
18
19 fn run(&self, module: &mut Module) -> bool {
20 let mut changed = false;
21 changed |= fold_expression_arena(&mut module.global_expressions);
22 for (_, func) in module.functions.iter_mut() {
23 changed |= fold_expression_arena(&mut func.expressions);
24 }
25 for ep in &mut module.entry_points {
26 changed |= fold_expression_arena(&mut ep.function.expressions);
27 }
28 changed
29 }
30}
31
32fn fold_expression_arena(arena: &mut Arena<Expression>) -> bool {
33 let mut changed = false;
34
35 let handles: Vec<Handle<Expression>> = arena.iter().map(|(h, _)| h).collect();
36
37 for handle in handles {
38 let replacement = match &arena[handle] {
39 Expression::Binary { op, left, right } => {
40 let left_val = &arena[*left];
41 let right_val = &arena[*right];
42 if let (Expression::Literal(l), Expression::Literal(r)) = (left_val, right_val) {
43 fold_binary(*op, *l, *r).map(Expression::Literal)
44 } else {
45 None
46 }
47 }
48 Expression::Unary { op, expr } => {
49 if let Expression::Literal(lit) = &arena[*expr] {
50 fold_unary(*op, *lit).map(Expression::Literal)
51 } else {
52 None
53 }
54 }
55 Expression::Math {
56 fun,
57 arg,
58 arg1,
59 arg2,
60 arg3: _,
61 } => fold_math(
62 *fun,
63 &arena[*arg],
64 arg1.map(|h| &arena[h]),
65 arg2.map(|h| &arena[h]),
66 )
67 .map(Expression::Literal),
68 _ => None,
69 };
70
71 if let Some(new_expr) = replacement {
72 arena[handle] = new_expr;
73 changed = true;
74 }
75 }
76
77 changed
78}
79
80fn fold_binary(op: BinaryOp, left: Literal, right: Literal) -> Option<Literal> {
81 match (left, right) {
82 (Literal::F32(l), Literal::F32(r)) => fold_f32(op, l, r),
83 (Literal::I32(l), Literal::I32(r)) => fold_i32(op, l, r),
84 (Literal::U32(l), Literal::U32(r)) => fold_u32(op, l, r),
85 (Literal::Bool(l), Literal::Bool(r)) => fold_bool(op, l, r),
86 _ => None,
87 }
88}
89
90fn finite(v: f32) -> Option<f32> {
93 if v.is_finite() { Some(v) } else { None }
94}
95
96fn fold_f32(op: BinaryOp, l: f32, r: f32) -> Option<Literal> {
97 match op {
98 BinaryOp::Add => finite(l + r).map(Literal::F32),
99 BinaryOp::Subtract => finite(l - r).map(Literal::F32),
100 BinaryOp::Multiply => finite(l * r).map(Literal::F32),
101 BinaryOp::Divide => finite(l / r).map(Literal::F32),
102 BinaryOp::Modulo => finite(l % r).map(Literal::F32),
103 BinaryOp::Equal => Some(Literal::Bool(l == r)),
104 BinaryOp::NotEqual => Some(Literal::Bool(l != r)),
105 BinaryOp::Less => Some(Literal::Bool(l < r)),
106 BinaryOp::LessEqual => Some(Literal::Bool(l <= r)),
107 BinaryOp::Greater => Some(Literal::Bool(l > r)),
108 BinaryOp::GreaterEqual => Some(Literal::Bool(l >= r)),
109 _ => None,
110 }
111}
112
113fn fold_i32(op: BinaryOp, l: i32, r: i32) -> Option<Literal> {
114 match op {
115 BinaryOp::Add => Some(Literal::I32(l.wrapping_add(r))),
116 BinaryOp::Subtract => Some(Literal::I32(l.wrapping_sub(r))),
117 BinaryOp::Multiply => Some(Literal::I32(l.wrapping_mul(r))),
118 BinaryOp::Divide if r != 0 => Some(Literal::I32(l.wrapping_div(r))),
119 BinaryOp::Modulo if r != 0 => Some(Literal::I32(l.wrapping_rem(r))),
120 BinaryOp::Equal => Some(Literal::Bool(l == r)),
121 BinaryOp::NotEqual => Some(Literal::Bool(l != r)),
122 BinaryOp::Less => Some(Literal::Bool(l < r)),
123 BinaryOp::LessEqual => Some(Literal::Bool(l <= r)),
124 BinaryOp::Greater => Some(Literal::Bool(l > r)),
125 BinaryOp::GreaterEqual => Some(Literal::Bool(l >= r)),
126 BinaryOp::BitwiseAnd => Some(Literal::I32(l & r)),
127 BinaryOp::BitwiseOr => Some(Literal::I32(l | r)),
128 BinaryOp::BitwiseXor => Some(Literal::I32(l ^ r)),
129 BinaryOp::ShiftLeft => Some(Literal::I32(l.wrapping_shl(r as u32))),
130 BinaryOp::ShiftRight => Some(Literal::I32(l.wrapping_shr(r as u32))),
131 _ => None,
132 }
133}
134
135fn fold_u32(op: BinaryOp, l: u32, r: u32) -> Option<Literal> {
136 match op {
137 BinaryOp::Add => Some(Literal::U32(l.wrapping_add(r))),
138 BinaryOp::Subtract => Some(Literal::U32(l.wrapping_sub(r))),
139 BinaryOp::Multiply => Some(Literal::U32(l.wrapping_mul(r))),
140 BinaryOp::Divide if r != 0 => Some(Literal::U32(l / r)),
141 BinaryOp::Modulo if r != 0 => Some(Literal::U32(l % r)),
142 BinaryOp::Equal => Some(Literal::Bool(l == r)),
143 BinaryOp::NotEqual => Some(Literal::Bool(l != r)),
144 BinaryOp::Less => Some(Literal::Bool(l < r)),
145 BinaryOp::LessEqual => Some(Literal::Bool(l <= r)),
146 BinaryOp::Greater => Some(Literal::Bool(l > r)),
147 BinaryOp::GreaterEqual => Some(Literal::Bool(l >= r)),
148 BinaryOp::BitwiseAnd => Some(Literal::U32(l & r)),
149 BinaryOp::BitwiseOr => Some(Literal::U32(l | r)),
150 BinaryOp::BitwiseXor => Some(Literal::U32(l ^ r)),
151 BinaryOp::ShiftLeft => Some(Literal::U32(l.wrapping_shl(r))),
152 BinaryOp::ShiftRight => Some(Literal::U32(l.wrapping_shr(r))),
153 _ => None,
154 }
155}
156
157fn fold_bool(op: BinaryOp, l: bool, r: bool) -> Option<Literal> {
158 match op {
159 BinaryOp::Equal => Some(Literal::Bool(l == r)),
160 BinaryOp::NotEqual => Some(Literal::Bool(l != r)),
161 BinaryOp::LogicalAnd => Some(Literal::Bool(l && r)),
162 BinaryOp::LogicalOr => Some(Literal::Bool(l || r)),
163 _ => None,
164 }
165}
166
167fn fold_unary(op: UnaryOp, lit: Literal) -> Option<Literal> {
168 match (op, lit) {
169 (UnaryOp::Negate, Literal::F32(v)) => finite(-v).map(Literal::F32),
170 (UnaryOp::Negate, Literal::I32(v)) => Some(Literal::I32(v.wrapping_neg())),
171 (UnaryOp::LogicalNot, Literal::Bool(v)) => Some(Literal::Bool(!v)),
172 (UnaryOp::BitwiseNot, Literal::I32(v)) => Some(Literal::I32(!v)),
173 (UnaryOp::BitwiseNot, Literal::U32(v)) => Some(Literal::U32(!v)),
174 _ => None,
175 }
176}
177
178fn fold_math(
179 fun: MathFunction,
180 arg: &Expression,
181 arg1: Option<&Expression>,
182 arg2: Option<&Expression>,
183) -> Option<Literal> {
184 let a = match arg {
186 Expression::Literal(Literal::F32(v)) => *v,
187 _ => return None,
188 };
189 let b = arg1.and_then(|e| match e {
190 Expression::Literal(Literal::F32(v)) => Some(*v),
191 _ => None,
192 });
193 let c = arg2.and_then(|e| match e {
194 Expression::Literal(Literal::F32(v)) => Some(*v),
195 _ => None,
196 });
197
198 let result = match fun {
199 MathFunction::Abs => Some(a.abs()),
201 MathFunction::Floor => Some(a.floor()),
202 MathFunction::Ceil => Some(a.ceil()),
203 MathFunction::Round => Some(a.round()),
204 MathFunction::Fract => Some(a.fract()),
205 MathFunction::Trunc => Some(a.trunc()),
206 MathFunction::Sqrt => Some(a.sqrt()),
207 MathFunction::InverseSqrt => Some(1.0 / a.sqrt()),
208 MathFunction::Log => Some(a.ln()),
209 MathFunction::Log2 => Some(a.log2()),
210 MathFunction::Exp => Some(a.exp()),
211 MathFunction::Exp2 => Some(a.exp2()),
212 MathFunction::Sin => Some(a.sin()),
213 MathFunction::Cos => Some(a.cos()),
214 MathFunction::Tan => Some(a.tan()),
215 MathFunction::Asin => Some(a.asin()),
216 MathFunction::Acos => Some(a.acos()),
217 MathFunction::Atan => Some(a.atan()),
218 MathFunction::Sinh => Some(a.sinh()),
219 MathFunction::Cosh => Some(a.cosh()),
220 MathFunction::Tanh => Some(a.tanh()),
221 MathFunction::Saturate => Some(a.clamp(0.0, 1.0)),
222 MathFunction::Min => Some(a.min(b?)),
224 MathFunction::Max => Some(a.max(b?)),
225 MathFunction::Pow => Some(a.powf(b?)),
226 MathFunction::Atan2 => Some(a.atan2(b?)),
227 MathFunction::Step => {
228 let x = b?;
231 Some(if x < a { 0.0 } else { 1.0 })
232 }
233 MathFunction::Clamp => {
235 let lo = b?;
236 let hi = c?;
237 Some(a.clamp(lo, hi))
238 }
239 MathFunction::Mix => {
240 let y = b?;
241 let t = c?;
242 Some(a * (1.0 - t) + y * t)
243 }
244 MathFunction::Fma => {
245 let mb = b?;
246 let mc = c?;
247 Some(a.mul_add(mb, mc))
248 }
249 MathFunction::Dot
251 | MathFunction::Cross
252 | MathFunction::Normalize
253 | MathFunction::Length
254 | MathFunction::Distance
255 | MathFunction::SmoothStep => None,
256 MathFunction::ExtractBits | MathFunction::InsertBits => None,
260 };
261
262 result.and_then(finite).map(Literal::F32)
263}
264
265#[cfg(test)]
266mod bit_op_folding_tests {
267 use super::*;
268 use nxpu_ir::{Expression, Function, Literal, MathFunction};
269
270 #[test]
271 fn bit_manipulation_is_not_folded() {
272 let mut func = Function::new("test");
275 let a = func
276 .expressions
277 .append(Expression::Literal(Literal::F32(8.0)));
278 let h = func.expressions.append(Expression::Math {
279 fun: MathFunction::ExtractBits,
280 arg: a,
281 arg1: Some(a),
282 arg2: Some(a),
283 arg3: None,
284 });
285 let before = format!("{:?}", func.expressions[h]);
286 fold_expression_arena(&mut func.expressions);
287 assert_eq!(format!("{:?}", func.expressions[h]), before);
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use nxpu_ir::{Function, Literal};
295
296 #[test]
297 fn fold_f32_add() {
298 let mut func = Function::new("test");
299 let one = func
300 .expressions
301 .append(Expression::Literal(Literal::F32(1.0)));
302 let two = func
303 .expressions
304 .append(Expression::Literal(Literal::F32(2.0)));
305 let add = func.expressions.append(Expression::Binary {
306 op: BinaryOp::Add,
307 left: one,
308 right: two,
309 });
310
311 let changed = fold_expression_arena(&mut func.expressions);
312 assert!(changed);
313 match &func.expressions[add] {
314 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 3.0),
315 other => panic!("expected Literal(F32(3.0)), got {other:?}"),
316 }
317 }
318
319 #[test]
320 fn fold_i32_multiply() {
321 let mut func = Function::new("test");
322 let three = func
323 .expressions
324 .append(Expression::Literal(Literal::I32(3)));
325 let four = func
326 .expressions
327 .append(Expression::Literal(Literal::I32(4)));
328 let mul = func.expressions.append(Expression::Binary {
329 op: BinaryOp::Multiply,
330 left: three,
331 right: four,
332 });
333
334 let changed = fold_expression_arena(&mut func.expressions);
335 assert!(changed);
336 match &func.expressions[mul] {
337 Expression::Literal(Literal::I32(v)) => assert_eq!(*v, 12),
338 other => panic!("expected Literal(I32(12)), got {other:?}"),
339 }
340 }
341
342 #[test]
343 fn fold_unary_negate() {
344 let mut func = Function::new("test");
345 let five = func
346 .expressions
347 .append(Expression::Literal(Literal::F32(5.0)));
348 let neg = func.expressions.append(Expression::Unary {
349 op: UnaryOp::Negate,
350 expr: five,
351 });
352
353 let changed = fold_expression_arena(&mut func.expressions);
354 assert!(changed);
355 match &func.expressions[neg] {
356 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, -5.0),
357 other => panic!("expected Literal(F32(-5.0)), got {other:?}"),
358 }
359 }
360
361 #[test]
362 fn no_fold_non_literal_operands() {
363 let mut func = Function::new("test");
364 let arg = func.expressions.append(Expression::FunctionArgument(0));
365 let lit = func
366 .expressions
367 .append(Expression::Literal(Literal::F32(2.0)));
368 let _add = func.expressions.append(Expression::Binary {
369 op: BinaryOp::Add,
370 left: arg,
371 right: lit,
372 });
373
374 let changed = fold_expression_arena(&mut func.expressions);
375 assert!(!changed);
376 }
377
378 #[test]
379 fn fold_comparison() {
380 let mut func = Function::new("test");
381 let a = func
382 .expressions
383 .append(Expression::Literal(Literal::U32(5)));
384 let b = func
385 .expressions
386 .append(Expression::Literal(Literal::U32(3)));
387 let cmp = func.expressions.append(Expression::Binary {
388 op: BinaryOp::Greater,
389 left: a,
390 right: b,
391 });
392
393 let changed = fold_expression_arena(&mut func.expressions);
394 assert!(changed);
395 match &func.expressions[cmp] {
396 Expression::Literal(Literal::Bool(v)) => assert!(*v),
397 other => panic!("expected Literal(Bool(true)), got {other:?}"),
398 }
399 }
400
401 #[test]
402 fn cascade_folding() {
403 let mut func = Function::new("test");
405 let a = func
406 .expressions
407 .append(Expression::Literal(Literal::F32(2.0)));
408 let b = func
409 .expressions
410 .append(Expression::Literal(Literal::F32(3.0)));
411 let mul = func.expressions.append(Expression::Binary {
412 op: BinaryOp::Multiply,
413 left: a,
414 right: b,
415 });
416 let c = func
417 .expressions
418 .append(Expression::Literal(Literal::F32(4.0)));
419 let add = func.expressions.append(Expression::Binary {
420 op: BinaryOp::Add,
421 left: mul,
422 right: c,
423 });
424
425 let changed = fold_expression_arena(&mut func.expressions);
426 assert!(changed);
427 match &func.expressions[add] {
429 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 10.0),
430 other => panic!("expected Literal(F32(10.0)), got {other:?}"),
431 }
432 }
433
434 fn make_math1(func: &mut Function, fun: MathFunction, val: f32) -> Handle<Expression> {
437 let arg = func
438 .expressions
439 .append(Expression::Literal(Literal::F32(val)));
440 func.expressions.append(Expression::Math {
441 fun,
442 arg,
443 arg1: None,
444 arg2: None,
445 arg3: None,
446 })
447 }
448
449 fn make_math2(func: &mut Function, fun: MathFunction, a: f32, b: f32) -> Handle<Expression> {
450 let arg = func
451 .expressions
452 .append(Expression::Literal(Literal::F32(a)));
453 let arg1 = func
454 .expressions
455 .append(Expression::Literal(Literal::F32(b)));
456 func.expressions.append(Expression::Math {
457 fun,
458 arg,
459 arg1: Some(arg1),
460 arg2: None,
461 arg3: None,
462 })
463 }
464
465 fn make_math3(
466 func: &mut Function,
467 fun: MathFunction,
468 a: f32,
469 b: f32,
470 c: f32,
471 ) -> Handle<Expression> {
472 let arg = func
473 .expressions
474 .append(Expression::Literal(Literal::F32(a)));
475 let arg1 = func
476 .expressions
477 .append(Expression::Literal(Literal::F32(b)));
478 let arg2 = func
479 .expressions
480 .append(Expression::Literal(Literal::F32(c)));
481 func.expressions.append(Expression::Math {
482 fun,
483 arg,
484 arg1: Some(arg1),
485 arg2: Some(arg2),
486 arg3: None,
487 })
488 }
489
490 #[test]
491 fn fold_math_abs() {
492 let mut func = Function::new("test");
493 let h = make_math1(&mut func, MathFunction::Abs, -3.0);
494 let changed = fold_expression_arena(&mut func.expressions);
495 assert!(changed);
496 match &func.expressions[h] {
497 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 3.0),
498 other => panic!("expected 3.0, got {other:?}"),
499 }
500 }
501
502 #[test]
503 fn fold_math_sqrt() {
504 let mut func = Function::new("test");
505 let h = make_math1(&mut func, MathFunction::Sqrt, 9.0);
506 let changed = fold_expression_arena(&mut func.expressions);
507 assert!(changed);
508 match &func.expressions[h] {
509 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 3.0),
510 other => panic!("expected 3.0, got {other:?}"),
511 }
512 }
513
514 #[test]
515 fn fold_math_min_max() {
516 let mut func = Function::new("test");
517 let min_h = make_math2(&mut func, MathFunction::Min, 2.0, 5.0);
518 let max_h = make_math2(&mut func, MathFunction::Max, 2.0, 5.0);
519 let changed = fold_expression_arena(&mut func.expressions);
520 assert!(changed);
521 match &func.expressions[min_h] {
522 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 2.0),
523 other => panic!("expected 2.0, got {other:?}"),
524 }
525 match &func.expressions[max_h] {
526 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 5.0),
527 other => panic!("expected 5.0, got {other:?}"),
528 }
529 }
530
531 #[test]
532 fn fold_math_clamp() {
533 let mut func = Function::new("test");
534 let h = make_math3(&mut func, MathFunction::Clamp, 10.0, 0.0, 5.0);
535 let changed = fold_expression_arena(&mut func.expressions);
536 assert!(changed);
537 match &func.expressions[h] {
538 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 5.0),
539 other => panic!("expected 5.0, got {other:?}"),
540 }
541 }
542
543 #[test]
544 fn fold_math_trig() {
545 let mut func = Function::new("test");
546 let h = make_math1(&mut func, MathFunction::Sin, 0.0);
547 let changed = fold_expression_arena(&mut func.expressions);
548 assert!(changed);
549 match &func.expressions[h] {
550 Expression::Literal(Literal::F32(v)) => assert!(v.abs() < 1e-6),
551 other => panic!("expected ~0.0, got {other:?}"),
552 }
553 }
554
555 #[test]
556 fn fold_math_fma() {
557 let mut func = Function::new("test");
558 let h = make_math3(&mut func, MathFunction::Fma, 2.0, 3.0, 4.0);
560 let changed = fold_expression_arena(&mut func.expressions);
561 assert!(changed);
562 match &func.expressions[h] {
563 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 10.0),
564 other => panic!("expected 10.0, got {other:?}"),
565 }
566 }
567
568 #[test]
569 fn no_fold_f32_div_by_zero() {
570 let mut func = Function::new("test");
571 let a = func
572 .expressions
573 .append(Expression::Literal(Literal::F32(1.0)));
574 let b = func
575 .expressions
576 .append(Expression::Literal(Literal::F32(0.0)));
577 let div = func.expressions.append(Expression::Binary {
578 op: BinaryOp::Divide,
579 left: a,
580 right: b,
581 });
582
583 let changed = fold_expression_arena(&mut func.expressions);
584 assert!(!changed);
585 assert!(matches!(&func.expressions[div], Expression::Binary { .. }));
587 }
588
589 #[test]
590 fn no_fold_sqrt_negative() {
591 let mut func = Function::new("test");
592 let h = make_math1(&mut func, MathFunction::Sqrt, -1.0);
593 let changed = fold_expression_arena(&mut func.expressions);
594 assert!(!changed);
595 assert!(matches!(&func.expressions[h], Expression::Math { .. }));
596 }
597
598 #[test]
599 fn no_fold_log_zero() {
600 let mut func = Function::new("test");
601 let h = make_math1(&mut func, MathFunction::Log, 0.0);
602 let changed = fold_expression_arena(&mut func.expressions);
603 assert!(!changed);
604 assert!(matches!(&func.expressions[h], Expression::Math { .. }));
605 }
606
607 #[test]
608 fn no_fold_math_non_literal() {
609 let mut func = Function::new("test");
610 let arg = func.expressions.append(Expression::FunctionArgument(0));
611 let _h = func.expressions.append(Expression::Math {
612 fun: MathFunction::Abs,
613 arg,
614 arg1: None,
615 arg2: None,
616 arg3: None,
617 });
618 let changed = fold_expression_arena(&mut func.expressions);
619 assert!(!changed);
620 }
621
622 #[test]
623 fn fold_global_expression() {
624 use nxpu_ir::Module;
625
626 let mut module = Module::default();
627 let one = module
628 .global_expressions
629 .append(Expression::Literal(Literal::F32(2.0)));
630 let two = module
631 .global_expressions
632 .append(Expression::Literal(Literal::F32(3.0)));
633 let add = module.global_expressions.append(Expression::Binary {
634 op: BinaryOp::Add,
635 left: one,
636 right: two,
637 });
638
639 let pass = ConstantFolding;
640 let changed = pass.run(&mut module);
641 assert!(changed);
642 match &module.global_expressions[add] {
643 Expression::Literal(Literal::F32(v)) => assert_eq!(*v, 5.0),
644 other => panic!("expected Literal(F32(5.0)), got {other:?}"),
645 }
646 }
647}