1use alloc::{vec, vec::Vec};
2use cubecl_ir::{
3 Arithmetic, Bitwise, Comparison, ConstantValue, GlobalState, Instruction, Operation, Operator,
4 Type, Value,
5};
6
7use crate::post_processing::{
8 analysis_helper::GlobalAnalyses, util::AtomicCounter, visitor::InstructionVisitor,
9};
10
11#[derive(Debug)]
14pub struct ConstOperandSimplify;
15
16impl InstructionVisitor for ConstOperandSimplify {
17 fn visit_instruction(
18 &mut self,
19 mut inst: Instruction,
20 _state: &GlobalState,
21 _analyses: &GlobalAnalyses,
22 changes: &AtomicCounter,
23 ) -> Vec<Instruction> {
24 match &mut inst.operation {
25 Operation::Arithmetic(operator) => match operator {
26 Arithmetic::Mul(bin_op) if bin_op.lhs.is_constant(0) => {
28 inst.operation = Operation::Copy(bin_op.lhs);
29 changes.inc();
30 }
31 Arithmetic::Mul(bin_op) if bin_op.rhs.is_constant(0) => {
33 inst.operation = Operation::Copy(bin_op.rhs);
34 changes.inc();
35 }
36 Arithmetic::Mul(bin_op) if bin_op.lhs.is_constant(1) => {
38 inst.operation = Operation::Copy(bin_op.rhs);
39 changes.inc();
40 }
41 Arithmetic::Mul(bin_op) if bin_op.rhs.is_constant(1) => {
43 inst.operation = Operation::Copy(bin_op.lhs);
44 changes.inc();
45 }
46 Arithmetic::Add(bin_op) if bin_op.lhs.is_constant(0) => {
48 inst.operation = Operation::Copy(bin_op.rhs);
49 changes.inc();
50 }
51 Arithmetic::Add(bin_op) if bin_op.rhs.is_constant(0) => {
53 inst.operation = Operation::Copy(bin_op.lhs);
54 changes.inc();
55 }
56 Arithmetic::Sub(bin_op) if bin_op.rhs.is_constant(0) => {
58 inst.operation = Operation::Copy(bin_op.lhs);
59 changes.inc();
60 }
61 Arithmetic::Div(bin_op)
63 if bin_op.lhs.is_constant(0) || bin_op.rhs.is_constant(1) =>
64 {
65 inst.operation = Operation::Copy(bin_op.lhs);
66 changes.inc();
67 }
68 Arithmetic::ModFloor(bin_op) | Arithmetic::Rem(bin_op)
70 if bin_op.rhs.is_constant(1) || bin_op.lhs.is_constant(0) =>
71 {
72 inst.operation = Operation::Copy(inst.ty().constant(ConstantValue::Int(0)));
73 changes.inc();
74 }
75 _ => {}
76 },
77
78 Operation::Operator(operator) => match operator {
79 Operator::Or(bin_op) if bin_op.lhs.is_true() || bin_op.rhs.is_true() => {
81 inst.operation = Operation::Copy(true.into());
82 changes.inc();
83 }
84 Operator::Or(bin_op) if bin_op.lhs.is_false() => {
86 inst.operation = Operation::Copy(bin_op.rhs);
87 changes.inc();
88 }
89 Operator::Or(bin_op) if bin_op.rhs.is_false() => {
91 inst.operation = Operation::Copy(bin_op.lhs);
92 changes.inc();
93 }
94 Operator::And(bin_op) if bin_op.lhs.is_false() || bin_op.rhs.is_false() => {
96 inst.operation = Operation::Copy(false.into());
97 changes.inc();
98 }
99 Operator::And(bin_op) if bin_op.lhs.is_true() => {
101 inst.operation = Operation::Copy(bin_op.rhs);
102 changes.inc();
103 }
104 Operator::And(bin_op) if bin_op.rhs.is_true() => {
106 inst.operation = Operation::Copy(bin_op.lhs);
107 changes.inc();
108 }
109 Operator::Select(select) if select.cond.is_true() => {
111 inst.operation = Operation::Copy(select.then);
112 changes.inc();
113 }
114 Operator::Select(select) if select.cond.is_false() => {
116 inst.operation = Operation::Copy(select.or_else);
117 changes.inc();
118 }
119 _ => {}
120 },
121 _ => {}
122 };
123 vec![inst]
124 }
125}
126
127#[derive(Debug)]
131pub struct ConstEval;
132
133impl InstructionVisitor for ConstEval {
134 fn visit_instruction(
135 &mut self,
136 mut inst: Instruction,
137 _state: &GlobalState,
138 _analyses: &GlobalAnalyses,
139 changes: &AtomicCounter,
140 ) -> Vec<Instruction> {
141 if let Some(const_eval) = try_const_eval(&mut inst) {
142 let input = Value::constant(const_eval, inst.out().ty);
143 inst.operation = Operation::Copy(input);
144 changes.inc();
145 }
146 vec![inst]
147 }
148}
149
150macro_rules! const_eval {
151 ($op:tt $lhs:expr, $rhs:expr) => {{
152 use ConstantValue::*;
153
154 let ty = $lhs.ty;
155 let lhs = $lhs.as_const();
156 let rhs = $rhs.as_const();
157 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
158 let rhs = rhs.cast_to(ty);
159 Some(match (lhs, rhs) {
160 (Int(lhs), Int(rhs)) => ConstantValue::Int(lhs $op rhs),
161 (Float(lhs), Float(rhs)) => ConstantValue::Float(lhs $op rhs),
162 (UInt(lhs), UInt(rhs)) => ConstantValue::UInt(lhs $op rhs),
163 _ => unreachable!(),
164 })
165 } else {
166 None
167 }
168 }};
169}
170
171macro_rules! const_eval_int {
172 ($op:tt $lhs:expr, $rhs:expr) => {{
173 use ConstantValue::*;
174
175 let ty = $lhs.ty;
176 let lhs = $lhs.as_const();
177 let rhs = $rhs.as_const();
178 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
179 let rhs = rhs.cast_to(ty);
180 Some(match (lhs, rhs) {
181 (Int(lhs), Int(rhs)) => Int(lhs $op rhs),
182 (UInt(lhs), UInt(rhs)) => UInt(lhs $op rhs),
183 _ => unreachable!(),
184 })
185 } else {
186 None
187 }
188 }};
189 ($lhs:expr, $rhs:expr; $op:path) => {{
190 use ConstantValue::*;
191
192 let ty = $lhs.ty;
193 let lhs = $lhs.as_const();
194 let rhs = $rhs.as_const();
195 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
196 let rhs = rhs.cast_to(ty);
197 Some(match (lhs, rhs) {
198 (Int(lhs), Int(rhs)) => Int($op(lhs, rhs)),
199 (UInt(lhs), UInt(rhs)) => UInt($op(lhs, rhs)),
200 _ => unreachable!(),
201 })
202 } else {
203 None
204 }
205 }};
206}
207
208macro_rules! const_eval_float {
209 ($lhs:expr, $rhs:expr; $fn:path) => {{
210 use ConstantValue::*;
211
212 let ty = $lhs.ty;
213 let lhs = $lhs.as_const();
214 let rhs = $rhs.as_const();
215 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
216 let rhs = rhs.cast_to(ty);
217 Some(match (lhs, rhs) {
218 (Float(lhs), Float(rhs)) => ConstantValue::Float($fn(lhs, rhs)),
219 _ => unreachable!(),
220 })
221 } else {
222 None
223 }
224 }};
225 ($input:expr; $fn:path) => {{
226 use ConstantValue::*;
227
228 if let Some(input) = $input.as_const() {
229 Some(match input {
230 Float(input) => ConstantValue::Float($fn(input)),
231 _ => unreachable!(),
232 })
233 } else {
234 None
235 }
236 }};
237}
238
239macro_rules! const_eval_cmp {
240 ($op:tt $lhs:expr, $rhs:expr) => {{
241 use ConstantValue::*;
242
243 let lhs = $lhs.as_const();
244 let rhs = $rhs.as_const();
245 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
246 Some(match (lhs, rhs) {
247 (Int(lhs), Int(rhs)) => ConstantValue::Bool(lhs $op rhs),
248 (Float(lhs), Float(rhs)) => ConstantValue::Bool(lhs $op rhs),
249 (UInt(lhs), UInt(rhs)) => ConstantValue::Bool(lhs $op rhs),
250 _ => unreachable!(),
251 })
252 } else {
253 None
254 }
255 }};
256}
257
258macro_rules! const_eval_bool {
259 ($op:tt $lhs:expr, $rhs:expr) => {{
260 use ConstantValue::*;
261
262 let lhs = $lhs.as_const();
263 let rhs = $rhs.as_const();
264 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
265 Some(match (lhs, rhs) {
266 (Bool(lhs), Bool(rhs)) => ConstantValue::Bool(lhs $op rhs),
267 _ => unreachable!(),
268 })
269 } else {
270 None
271 }
272 }};
273}
274
275fn try_const_eval(inst: &mut Instruction) -> Option<ConstantValue> {
276 match &mut inst.operation {
277 Operation::Arithmetic(op) => try_const_eval_arithmetic(op),
278 Operation::Comparison(op) => try_const_eval_cmp(op),
279 Operation::Bitwise(op) => try_const_eval_bitwise(op),
280 Operation::Operator(op) => try_const_eval_operator(op, inst.out.map(|it| it.ty)),
281 _ => None,
282 }
283}
284
285fn try_const_eval_arithmetic(op: &mut Arithmetic) -> Option<ConstantValue> {
286 match op {
287 Arithmetic::Add(op) => const_eval!(+ op.lhs, op.rhs),
288 Arithmetic::Sub(op) => const_eval!(-op.lhs, op.rhs),
289 Arithmetic::Mul(op) => const_eval!(*op.lhs, op.rhs),
290 Arithmetic::Div(op) => const_eval!(/ op.lhs, op.rhs),
291 Arithmetic::SaturatingAdd(op) => {
292 use ConstantValue::*;
293
294 let ty = op.lhs.ty;
295 let lhs = op.lhs.as_const();
296 let rhs = op.rhs.as_const();
297 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
298 let rhs = rhs.cast_to(ty);
299 let width = ty.size();
300 Some(match (lhs, rhs, width) {
301 (Int(lhs), Int(rhs), 4) => Int((lhs as i32).saturating_add(rhs as i32) as i64),
302 (Int(lhs), Int(rhs), 8) => Int(lhs.saturating_add(rhs)),
303 (UInt(lhs), UInt(rhs), 4) => {
304 UInt((lhs as u32).saturating_add(rhs as u32) as u64)
305 }
306 (UInt(lhs), UInt(rhs), 8) => UInt(lhs.saturating_add(rhs)),
307 _ => unreachable!(),
308 })
309 } else {
310 None
311 }
312 }
313 Arithmetic::SaturatingSub(op) => {
314 use ConstantValue::*;
315
316 let ty = op.lhs.ty;
317 let lhs = op.lhs.as_const();
318 let rhs = op.rhs.as_const();
319 if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
320 let rhs = rhs.cast_to(ty);
321 let width = ty.size();
322 Some(match (lhs, rhs, width) {
323 (Int(lhs), Int(rhs), 4) => Int((lhs as i32).saturating_sub(rhs as i32) as i64),
324 (Int(lhs), Int(rhs), 8) => Int(lhs.saturating_sub(rhs)),
325 (UInt(lhs), UInt(rhs), 4) => {
326 UInt((lhs as u32).saturating_sub(rhs as u32) as u64)
327 }
328 (UInt(lhs), UInt(rhs), 8) => UInt(lhs.saturating_sub(rhs)),
329 _ => unreachable!(),
330 })
331 } else {
332 None
333 }
334 }
335 Arithmetic::Powf(op) => const_eval_float!(op.lhs, op.rhs; num_traits::Float::powf),
336 Arithmetic::Powi(op) => {
338 const_eval_float!(op.lhs, op.rhs; num_traits::Float::powf)
339 }
340 Arithmetic::ModFloor(op) => const_eval_int!(op.lhs, op.rhs; num_integer::mod_floor),
341 Arithmetic::Rem(op) => const_eval!(% op.lhs, op.rhs),
342 Arithmetic::MulHi(op) => {
343 use ConstantValue::*;
344 let ty = op.lhs.ty;
345 if let (Some(lhs), Some(rhs)) = (op.lhs.as_const(), op.rhs.as_const()) {
346 let rhs = rhs.cast_to(ty);
347 Some(match (lhs, rhs) {
348 (Int(lhs), Int(rhs)) => {
349 let mul = (lhs * rhs) >> 32;
350 ConstantValue::Int(mul as i32 as i64)
351 }
352 (UInt(lhs), UInt(rhs)) => {
353 let mul = (lhs * rhs) >> 32;
354 ConstantValue::UInt(mul as u32 as u64)
355 }
356 _ => unreachable!(),
357 })
358 } else {
359 None
360 }
361 }
362 Arithmetic::Max(op) => {
363 use ConstantValue::*;
364 let ty = op.lhs.ty;
365 if let (Some(lhs), Some(rhs)) = (op.lhs.as_const(), op.rhs.as_const()) {
366 let rhs = rhs.cast_to(ty);
367 Some(match (lhs, rhs) {
368 (Int(lhs), Int(rhs)) => ConstantValue::Int(lhs.max(rhs)),
369 (Float(lhs), Float(rhs)) => ConstantValue::Float(lhs.max(rhs)),
370 (UInt(lhs), UInt(rhs)) => ConstantValue::UInt(lhs.max(rhs)),
371 _ => unreachable!(),
372 })
373 } else {
374 None
375 }
376 }
377 Arithmetic::Min(op) => {
378 use ConstantValue::*;
379 let ty = op.lhs.ty;
380 if let (Some(lhs), Some(rhs)) = (op.lhs.as_const(), op.rhs.as_const()) {
381 let rhs = rhs.cast_to(ty);
382 Some(match (lhs, rhs) {
383 (Int(lhs), Int(rhs)) => ConstantValue::Int(lhs.min(rhs)),
384 (Float(lhs), Float(rhs)) => ConstantValue::Float(lhs.min(rhs)),
385 (UInt(lhs), UInt(rhs)) => ConstantValue::UInt(lhs.min(rhs)),
386 _ => unreachable!(),
387 })
388 } else {
389 None
390 }
391 }
392 Arithmetic::Dot(op) => const_eval!(*op.lhs, op.rhs),
393
394 Arithmetic::Abs(op) => {
395 use ConstantValue::*;
396 op.input.as_const().map(|input| match input {
397 Int(input) => ConstantValue::Int(input.abs()),
398 Float(input) => ConstantValue::Float(input.abs()),
399 _ => unreachable!(),
400 })
401 }
402 Arithmetic::Exp(op) => const_eval_float!(op.input; num_traits::Float::exp),
403 Arithmetic::Log(op) => const_eval_float!(op.input; num_traits::Float::ln),
404 Arithmetic::Log1p(op) => const_eval_float!(op.input; num_traits::Float::ln_1p),
405 Arithmetic::Expm1(op) => const_eval_float!(op.input; num_traits::Float::exp_m1),
406 Arithmetic::Cos(op) => const_eval_float!(op.input; num_traits::Float::cos),
407 Arithmetic::Sin(op) => const_eval_float!(op.input; num_traits::Float::sin),
408 Arithmetic::Tan(op) => const_eval_float!(op.input; num_traits::Float::tan),
409 Arithmetic::Tanh(op) => const_eval_float!(op.input; num_traits::Float::tanh),
410 Arithmetic::Sinh(op) => const_eval_float!(op.input; num_traits::Float::sinh),
411 Arithmetic::Cosh(op) => const_eval_float!(op.input; num_traits::Float::cosh),
412 Arithmetic::ArcCos(op) => const_eval_float!(op.input; num_traits::Float::acos),
413 Arithmetic::ArcSin(op) => const_eval_float!(op.input; num_traits::Float::asin),
414 Arithmetic::ArcTan(op) => const_eval_float!(op.input; num_traits::Float::atan),
415 Arithmetic::ArcSinh(op) => const_eval_float!(op.input; num_traits::Float::asinh),
416 Arithmetic::ArcCosh(op) => const_eval_float!(op.input; num_traits::Float::acosh),
417 Arithmetic::ArcTanh(op) => const_eval_float!(op.input; num_traits::Float::atanh),
418 Arithmetic::Degrees(op) => const_eval_float!(op.input; num_traits::Float::to_degrees),
419 Arithmetic::Radians(op) => const_eval_float!(op.input; num_traits::Float::to_radians),
420 Arithmetic::ArcTan2(op) => const_eval_float!(op.lhs, op.rhs; num_traits::Float::atan2),
421 Arithmetic::Sqrt(op) => const_eval_float!(op.input; num_traits::Float::sqrt),
422 Arithmetic::Hypot(op) => const_eval_float!(op.lhs, op.rhs; num_traits::Float::hypot),
423 Arithmetic::Rhypot(op) => {
424 let hypot = const_eval_float!(op.lhs, op.rhs; num_traits::Float::hypot)?;
425 let ConstantValue::Float(val) = hypot else {
426 unreachable!()
427 };
428 Some(ConstantValue::Float(1.0 / val))
429 }
430 Arithmetic::InverseSqrt(op) => {
431 let sqrt = const_eval_float!(op.input; num_traits::Float::sqrt)?;
432 let ConstantValue::Float(val) = sqrt else {
433 unreachable!()
434 };
435 Some(ConstantValue::Float(1.0 / val))
436 }
437 Arithmetic::Round(op) => const_eval_float!(op.input; num_traits::Float::round),
438 Arithmetic::Floor(op) => const_eval_float!(op.input; num_traits::Float::floor),
439 Arithmetic::Ceil(op) => const_eval_float!(op.input; num_traits::Float::ceil),
440 Arithmetic::Trunc(op) => const_eval_float!(op.input; num_traits::Float::trunc),
441 Arithmetic::Recip(op) => const_eval_float!(op.input; num_traits::Float::recip),
442 Arithmetic::Neg(op) => {
443 use ConstantValue::*;
444 op.input.as_const().map(|input| match input {
445 Int(input) => ConstantValue::Int(-input),
446 Float(input) => ConstantValue::Float(-input),
447 _ => unreachable!(),
448 })
449 }
450
451 Arithmetic::Fma(op) => {
452 use ConstantValue::*;
453 let ty = op.a.ty;
454 let a = op.a.as_const();
455 let b = op.b.as_const();
456 let c = op.c.as_const();
457
458 a.zip(b).zip(c).map(|((a, b), c)| {
459 let b = b.cast_to(ty);
460 let c = c.cast_to(ty);
461 match (a, b, c) {
462 (Float(a), Float(b), Float(c)) => ConstantValue::Float(a * b + c),
463 _ => unreachable!(),
464 }
465 })
466 }
467 Arithmetic::Clamp(op) => {
468 use ConstantValue::*;
469 let ty = op.input.ty;
470 let a = op.input.as_const();
471 let b = op.min_value.as_const();
472 let c = op.max_value.as_const();
473
474 a.zip(b).zip(c).map(|((a, b), c)| {
475 let b = b.cast_to(ty);
476 let c = c.cast_to(ty);
477 match (a, b, c) {
478 (Int(a), Int(b), Int(c)) => ConstantValue::Int(a.clamp(b, c)),
479 (Float(a), Float(b), Float(c)) => ConstantValue::Float(a.clamp(b, c)),
480 (UInt(a), UInt(b), UInt(c)) => ConstantValue::UInt(a.clamp(b, c)),
481 _ => unreachable!(),
482 }
483 })
484 }
485 Arithmetic::Erf(_)
486 | Arithmetic::Magnitude(_)
487 | Arithmetic::Normalize(_)
488 | Arithmetic::VectorSum(_) => None,
489 }
490}
491
492fn try_const_eval_cmp(op: &mut Comparison) -> Option<ConstantValue> {
493 match op {
494 Comparison::Equal(op) => const_eval_cmp!(== op.lhs, op.rhs),
495 Comparison::NotEqual(op) => const_eval_cmp!(!= op.lhs, op.rhs),
496 Comparison::Lower(op) => const_eval_cmp!(< op.lhs, op.rhs),
497 Comparison::Greater(op) => const_eval_cmp!(> op.lhs, op.rhs),
498 Comparison::LowerEqual(op) => const_eval_cmp!(<= op.lhs, op.rhs),
499 Comparison::GreaterEqual(op) => const_eval_cmp!(>= op.lhs, op.rhs),
500 Comparison::IsNan(op) => {
501 use ConstantValue::*;
502 op.input.as_const().map(|input| match input {
503 Float(val) => Bool(val.is_nan()),
504 Int(_) | UInt(_) | Bool(_) => Bool(false),
506 })
507 }
508 Comparison::IsInf(op) => {
509 use ConstantValue::*;
510 op.input.as_const().map(|input| match input {
511 Float(val) => Bool(val.is_infinite()),
512 Int(_) | UInt(_) | Bool(_) => Bool(false),
514 })
515 }
516 }
517}
518
519fn try_const_eval_bitwise(op: &mut Bitwise) -> Option<ConstantValue> {
520 match op {
521 Bitwise::BitwiseAnd(op) => const_eval_int!(&op.lhs, op.rhs),
522 Bitwise::BitwiseOr(op) => const_eval_int!(| op.lhs, op.rhs),
523 Bitwise::BitwiseXor(op) => const_eval_int!(^ op.lhs, op.rhs),
524 Bitwise::ShiftLeft(op) => const_eval_int!(<< op.lhs, op.rhs),
525 Bitwise::ShiftRight(op) => const_eval_int!(>> op.lhs, op.rhs),
526 Bitwise::BitwiseNot(op) => {
527 use ConstantValue::*;
528 op.input.as_const().map(|input| match input {
529 Int(input) => ConstantValue::Int(!input),
530 UInt(input) => ConstantValue::UInt(!input),
531 _ => unreachable!(),
532 })
533 }
534 Bitwise::CountOnes(op) => {
535 use ConstantValue::*;
536 op.input.as_const().map(|input| match input {
537 Int(input) => ConstantValue::Int(input.count_ones() as i64),
538 UInt(input) => ConstantValue::UInt(input.count_ones() as u64),
539 _ => unreachable!(),
540 })
541 }
542 Bitwise::ReverseBits(op) => {
543 use ConstantValue::*;
544 op.input.as_const().map(|input| match input {
545 Int(input) => ConstantValue::Int(input.reverse_bits()),
546 UInt(input) => ConstantValue::UInt(input.reverse_bits()),
547 _ => unreachable!(),
548 })
549 }
550 Bitwise::LeadingZeros(_) | Bitwise::TrailingZeros(_) | Bitwise::FindFirstSet(_) => {
551 None
553 }
554 }
555}
556
557fn try_const_eval_operator(op: &mut Operator, out_ty: Option<Type>) -> Option<ConstantValue> {
558 match op {
559 Operator::And(op) => const_eval_bool!(&&op.lhs, op.rhs),
560 Operator::Or(op) => const_eval_bool!(|| op.lhs, op.rhs),
561 Operator::Not(op) => {
562 use ConstantValue::*;
563 op.input.as_const().map(|input| match input {
564 Bool(input) => ConstantValue::Bool(!input),
565 _ => unreachable!(),
566 })
567 }
568 Operator::Cast(op) => op.input.as_const().map(|val| val.cast_to(out_ty.unwrap())),
569 Operator::InitVector(_)
570 | Operator::InsertComponent(_)
571 | Operator::ExtractComponent(_)
572 | Operator::Reinterpret(_)
573 | Operator::Select(_)
574 | Operator::ReadBuiltin(_)
575 | Operator::ReadScalar(_) => None,
576 }
577}