Skip to main content

cubecl_ir/dialect/
math.rs

1use core::ops::Neg;
2
3use cubecl_macros_internal::{const_eval, cube_op, simplify};
4use half::{bf16, f16};
5use num::Integer;
6use num_traits::Float;
7use pliron::{
8    attribute::AttrObj,
9    builtin::{attributes::IntegerAttr, types::IntegerType},
10    utils::apint::{APInt, bw},
11};
12
13use crate::{
14    CanMaterialize, ConstantValue, NoMemoryEffect, NoSideEffects, Pure,
15    attributes::{BoolAttr, FloatAttr, IndexAttr, IntAttrExt},
16    dialect::{pure_binop, pure_unop},
17    interfaces::{TriviallyUnrollable, TypedExt},
18    prelude::*,
19    types::{VectorType, scalar::BoolType},
20};
21
22pure_unop!("math.s_abs", SAbsOp);
23const_eval!(SAbsOp, {
24    [IntegerAttr(i8, i16, i32, i64)]: |inp| inp.abs(),
25});
26
27pure_unop!("math.f_abs", FAbsOp);
28const_eval!(FAbsOp, {
29    [FloatAttr(f16, bf16, f32, f64)]: |inp| inp.abs(),
30});
31
32pure_unop!("math.exp", ExpOp);
33const_eval!(ExpOp, {
34    FloatAttr(f16, bf16, f32, f64): |inp| inp.exp(),
35});
36
37pure_unop!("math.log", LogOp);
38const_eval!(LogOp, {
39    FloatAttr(f16, bf16, f32, f64): |inp| inp.ln(),
40});
41
42pure_unop!("math.log1p", Log1pOp);
43const_eval!(Log1pOp, {
44    FloatAttr(f16, bf16, f32, f64): |inp| inp.ln_1p(),
45});
46
47pure_unop!("math.expm1", Expm1Op);
48const_eval!(Expm1Op, {
49    FloatAttr(f16, bf16, f32, f64): |inp| inp.exp_m1(),
50});
51
52pure_unop!("math.sin", SinOp);
53const_eval!(SinOp, {
54    FloatAttr(f16, bf16, f32, f64): |inp| inp.sin(),
55});
56
57pure_unop!("math.cos", CosOp);
58const_eval!(CosOp, {
59    FloatAttr(f16, bf16, f32, f64): |inp| inp.cos(),
60});
61
62pure_unop!("math.tan", TanOp);
63const_eval!(TanOp, {
64    FloatAttr(f16, bf16, f32, f64): |inp| inp.tan(),
65});
66
67pure_unop!("math.sinh", SinhOp);
68const_eval!(SinhOp, {
69    FloatAttr(f16, bf16, f32, f64): |inp| inp.sinh(),
70});
71
72pure_unop!("math.cosh", CoshOp);
73const_eval!(CoshOp, {
74    FloatAttr(f16, bf16, f32, f64): |inp| inp.cosh(),
75});
76
77pure_unop!("math.tanh", TanhOp);
78const_eval!(TanhOp, {
79    FloatAttr(f16, bf16, f32, f64): |inp| inp.tanh(),
80});
81
82pure_unop!("math.arcsin", ArcSinOp);
83const_eval!(ArcSinOp, {
84    FloatAttr(f16, bf16, f32, f64): |inp| inp.asin(),
85});
86
87pure_unop!("math.arccos", ArcCosOp);
88const_eval!(ArcCosOp, {
89    FloatAttr(f16, bf16, f32, f64): |inp| inp.acos(),
90});
91
92pure_unop!("math.arctan", ArcTanOp);
93const_eval!(ArcTanOp, {
94    FloatAttr(f16, bf16, f32, f64): |inp| inp.atan(),
95});
96
97pure_unop!("math.arcsinh", ArcSinhOp);
98const_eval!(ArcSinhOp, {
99    FloatAttr(f16, bf16, f32, f64): |inp| inp.asinh(),
100});
101
102pure_unop!("math.arccosh", ArcCoshOp);
103const_eval!(ArcCoshOp, {
104    FloatAttr(f16, bf16, f32, f64): |inp| inp.acosh(),
105});
106
107pure_unop!("math.arctanh", ArcTanhOp);
108const_eval!(ArcTanhOp, {
109    FloatAttr(f16, bf16, f32, f64): |inp| inp.atanh(),
110});
111
112pure_unop!("math.degrees", DegreesOp);
113const_eval!(DegreesOp, {
114    FloatAttr(f16, bf16, f32, f64): |inp| inp.to_degrees(),
115});
116
117pure_unop!("math.radians", RadiansOp);
118const_eval!(RadiansOp, {
119    FloatAttr(f16, bf16, f32, f64): |inp| inp.to_radians(),
120});
121
122pure_unop!("math.sqrt", SqrtOp);
123const_eval!(SqrtOp, {
124    FloatAttr(f16, bf16, f32, f64): |inp| inp.sqrt(),
125});
126
127pure_unop!("math.rsqrt", RsqrtOp);
128const_eval!(RsqrtOp, {
129    FloatAttr(f16, bf16, f32, f64): |inp| inp.sqrt().recip(),
130});
131
132pure_unop!("math.round", RoundOp);
133const_eval!(RoundOp, {
134    FloatAttr(f16, bf16, f32, f64): |inp| inp.round(),
135});
136
137pure_unop!("math.floor", FloorOp);
138const_eval!(FloorOp, {
139    FloatAttr(f16, bf16, f32, f64): |inp| inp.floor(),
140});
141
142pure_unop!("math.ceil", CeilOp);
143const_eval!(CeilOp, {
144    FloatAttr(f16, bf16, f32, f64): |inp| inp.ceil(),
145});
146
147pure_unop!("math.trunc", TruncOp);
148const_eval!(TruncOp, {
149    FloatAttr(f16, bf16, f32, f64): |inp| inp.trunc(),
150});
151
152pure_unop!("math.erf", ErfOp);
153// Unstable as of now, don't want to make a buggy version myself
154// const_eval!(ErfOp, {
155//     FloatAttr(f16, bf16, f32, f64): |inp| inp.erf(),
156// });
157
158pure_unop!("math.recip", RecipOp);
159const_eval!(RecipOp, {
160    FloatAttr(f16, bf16, f32, f64): |inp| inp.recip(),
161});
162
163pure_unop!("math.s_neg", SNegOp);
164const_eval!(SNegOp, {
165    [IntegerAttr(i8, i16, i32, i64)]: |inp| inp.neg(),
166});
167
168pure_unop!("math.f_neg", FNegOp);
169const_eval!(FNegOp, {
170    [FloatAttr(f16, bf16, f32, f64)]: |inp| inp.neg(),
171});
172
173#[cube_op(name = "math.is_nan")]
174#[result_ty(from_inputs = pred_result_ty)]
175#[op_interfaces(TriviallyUnrollable)]
176#[op_traits(Pure, CanMaterialize)]
177pub struct IsNanOp {
178    pub input: Value,
179}
180const_eval!(IsNanOp, {
181    FloatAttr(f16, bf16, f32, f64): |inp| -> BoolAttr { inp.is_nan().into() }
182});
183
184#[cube_op(name = "math.is_inf")]
185#[result_ty(from_inputs = pred_result_ty)]
186#[op_interfaces(TriviallyUnrollable)]
187#[op_traits(Pure, CanMaterialize)]
188pub struct IsInfOp {
189    pub input: Value,
190}
191const_eval!(IsInfOp, {
192    FloatAttr(f16, bf16, f32, f64): |inp| -> BoolAttr { inp.is_infinite().into() }
193});
194
195fn pred_result_ty(ctx: &Context, input: &Value) -> TypeHandle {
196    let vectorization = input.vector_size(ctx);
197    let bool = BoolType::get(ctx).into();
198    if vectorization == 1 {
199        bool
200    } else {
201        VectorType::get(ctx, bool, vectorization).into()
202    }
203}
204
205pure_binop!("math.i_add", IAddOp);
206const_eval!(IAddOp, {
207    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.wrapping_add(rhs),
208});
209simplify!(IAddOp, {
210    |lhs, _| match lhs?.as_const_val(ctx) {
211        ConstantValue::Int(0) | ConstantValue::UInt(0) => {
212            Some(self.rhs(ctx))
213        }
214        _ => None?,
215    },
216    |_, rhs| match rhs?.as_const_val(ctx) {
217        ConstantValue::Int(0) | ConstantValue::UInt(0) => {
218            Some(self.lhs(ctx))
219        }
220        _ => None?,
221    }
222});
223
224pure_binop!("math.f_add", FAddOp);
225const_eval!(FAddOp, {
226    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs + rhs
227});
228simplify!(FAddOp, {
229    |lhs, _| match lhs?.float_as_f64(ctx) {
230        Some(0.0) => Some(self.rhs(ctx)),
231        _ => None?,
232    },
233    |_, rhs| match rhs?.float_as_f64(ctx) {
234        Some(0.0) => Some(self.lhs(ctx)),
235        _ => None?,
236    }
237});
238
239pure_binop!("math.saturating_s_add", SaturatingSAddOp);
240const_eval!(SaturatingSAddOp, {
241    [IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.saturating_add(rhs)
242});
243simplify!(SaturatingSAddOp, {
244    |lhs, _| match lhs?.as_const_val(ctx) {
245        ConstantValue::Int(0) => Some(self.rhs(ctx)),
246        _ => None?,
247    },
248    |_, rhs| match rhs?.as_const_val(ctx) {
249        ConstantValue::Int(0) => Some(self.lhs(ctx)),
250        _ => None?,
251    }
252});
253
254pure_binop!("math.saturating_u_add", SaturatingUAddOp);
255const_eval!(SaturatingUAddOp, {
256    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.saturating_add(rhs)
257});
258simplify!(SaturatingUAddOp, {
259    |lhs, _| match lhs?.as_const_val(ctx) {
260        ConstantValue::UInt(0) => Some(self.rhs(ctx)),
261        _ => None?,
262    },
263    |_, rhs| match rhs?.as_const_val(ctx) {
264        ConstantValue::UInt(0) => Some(self.lhs(ctx)),
265        _ => None?,
266    }
267});
268
269pure_binop!("math.i_sub", ISubOp);
270const_eval!(ISubOp, {
271    [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.wrapping_sub(rhs),
272    // x - x -> 0
273    custom: |_, _| {
274        if self.lhs(ctx) == self.rhs(ctx) {
275            Some(int_attr(ctx, self.result_type(ctx), 0))
276        } else {
277            None
278        }
279    }
280});
281simplify!(ISubOp, {
282    |_, rhs| match rhs?.as_const_val(ctx) {
283        ConstantValue::Int(0) | ConstantValue::UInt(0) => Some(self.lhs(ctx)),
284        _ => None?,
285    }
286});
287
288pure_binop!("math.f_sub", FSubOp);
289const_eval!(FSubOp, {
290    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs - rhs,
291    // x - x -> 0
292    custom: |_, _| {
293        if self.lhs(ctx) == self.rhs(ctx) {
294            Some(float_attr(ctx, self.result_type(ctx), 0.0))
295        } else {
296            None
297        }
298    }
299});
300simplify!(FSubOp, {
301    |_, rhs| match rhs?.as_const_val(ctx) {
302        ConstantValue::Float(val) if val.to_bits() == 0 => Some(self.lhs(ctx)),
303        _ => None?,
304    }
305});
306
307pure_binop!("math.saturating_s_sub", SaturatingSSubOp);
308const_eval!(SaturatingSSubOp, {
309    [IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.saturating_sub(rhs)
310});
311simplify!(SaturatingSSubOp, {
312    |_, rhs| match rhs?.as_const_val(ctx) {
313        ConstantValue::Int(0) => Some(self.lhs(ctx)),
314        _ => None?,
315    }
316});
317
318pure_binop!("math.saturating_u_sub", SaturatingUSubOp);
319const_eval!(SaturatingUSubOp, {
320    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.saturating_sub(rhs)
321});
322simplify!(SaturatingUSubOp, {
323    |_, rhs| match rhs?.as_const_val(ctx) {
324        ConstantValue::UInt(0) => Some(self.lhs(ctx)),
325        _ => None?,
326    }
327});
328
329pure_binop!("math.i_mul", IMulOp);
330const_eval!(IMulOp, {
331    [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.wrapping_mul(rhs),
332    // 0 * x -> 0; x * 0 -> 0
333    custom: |lhs, rhs| {
334        let const_val = lhs.or(rhs)?;
335        Some(match const_val.as_const_val(ctx) {
336            ConstantValue::Int(0) | ConstantValue::UInt(0) => int_attr(ctx, const_val.get_type(ctx), 0),
337            _ => None?
338        })
339    }
340});
341simplify!(IMulOp, {
342    |lhs, _| match lhs?.as_const_val(ctx) {
343        ConstantValue::Int(1) | ConstantValue::UInt(1) => {
344            Some(self.rhs(ctx))
345        }
346        _ => None?,
347    },
348    |_, rhs| match rhs?.as_const_val(ctx) {
349        ConstantValue::Int(1) | ConstantValue::UInt(1) => {
350            Some(self.lhs(ctx))
351        }
352        _ => None?,
353    }
354});
355
356pure_binop!("math.f_mul", FMulOp);
357const_eval!(FMulOp, {
358    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs * rhs,
359    // 0 * x -> 0; x * 0 -> 0
360    custom: |lhs, rhs| {
361        let const_val = lhs.or(rhs)?;
362        Some(match const_val.float_as_f64(ctx) {
363            Some(0.0) => float_attr(ctx, const_val.get_type(ctx), 0.0),
364            _ => None?
365        })
366    }
367});
368simplify!(FMulOp, {
369    |lhs, _| match lhs?.float_as_f64(ctx) {
370        Some(1.0) => Some(self.rhs(ctx)),
371        _ => None?,
372    },
373    |_, rhs| match rhs?.float_as_f64(ctx) {
374        Some(1.0) => Some(self.lhs(ctx)),
375        _ => None?,
376    }
377});
378
379#[cube_op(name = "math.s_div")]
380#[result_ty(same_as = lhs)]
381#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
382#[op_traits(CanMaterialize, NoSideEffects, NoMemoryEffect)] // Not pure because divide by zero
383pub struct SDivOp {
384    pub lhs: Value,
385    pub rhs: Value,
386}
387const_eval!(SDivOp, {
388    [IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.wrapping_div(rhs),
389    // 0 / x -> 0
390    custom: |lhs, _| {
391        Some(match lhs?.as_const_val(ctx) {
392            ConstantValue::Int(0) => int_attr(ctx, lhs?.get_type(ctx), 0),
393            _ => None?
394        })
395    },
396    // x / x -> 1. Only for one lane: `int_attr` carries no vectorization either.
397    custom: |_, _| {
398        let result = self.get_result(ctx);
399        if self.lhs(ctx) == self.rhs(ctx) && result.vector_size(ctx) == 1 {
400            Some(int_attr(ctx, result.get_type(ctx), 1))
401        } else {
402            None
403        }
404    }
405});
406simplify!(SDivOp, {
407    |_, rhs| match rhs?.as_const_val(ctx) {
408        ConstantValue::Int(1) => Some(self.lhs(ctx)),
409        _ => None?,
410    }
411});
412
413#[cube_op(name = "math.u_div")]
414#[result_ty(same_as = lhs)]
415#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
416#[op_traits(CanMaterialize, NoSideEffects, NoMemoryEffect)] // Not pure because divide by zero
417pub struct UDivOp {
418    pub lhs: Value,
419    pub rhs: Value,
420}
421const_eval!(UDivOp, {
422    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.wrapping_div(rhs),
423    // 0 / x -> 0
424    custom: |lhs, _| {
425        Some(match lhs?.as_const_val(ctx) {
426            ConstantValue::UInt(0) => int_attr(ctx, lhs?.get_type(ctx), 0),
427            _ => None?
428        })
429    },
430    // x / x -> 1. Only for one lane: `int_attr` carries no vectorization either.
431    custom: |_, _| {
432        let result = self.get_result(ctx);
433        if self.lhs(ctx) == self.rhs(ctx) && result.vector_size(ctx) == 1 {
434            Some(int_attr(ctx, result.get_type(ctx), 1))
435        } else {
436            None
437        }
438    }
439});
440simplify!(UDivOp, {
441    |_, rhs| match rhs?.as_const_val(ctx) {
442        ConstantValue::UInt(1) => Some(self.lhs(ctx)),
443        _ => None?,
444    }
445});
446
447pure_binop!("math.f_div", FDivOp);
448const_eval!(FDivOp, {
449    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs / rhs,
450    // 0 / x -> 0
451    custom: |lhs, _| {
452        Some(match lhs?.float_as_f64(ctx) {
453            Some(0.0) => float_attr(ctx, lhs?.get_type(ctx), 0.0),
454            _ => None?
455        })
456    },
457});
458simplify!(FDivOp, {
459    |_, rhs| match rhs?.float_as_f64(ctx) {
460        Some(1.0) => Some(self.lhs(ctx)),
461        _ => None?,
462    }
463});
464
465pure_binop!("math.arc_tan2", ArcTan2Op);
466const_eval!(ArcTan2Op, {
467    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs.atan2(rhs),
468});
469
470pure_binop!("math.powf", PowfOp);
471const_eval!(PowfOp, {
472    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs.powf(rhs),
473});
474
475#[cube_op(name = "math.powi")]
476#[result_ty(same_as = lhs)]
477#[op_traits(Pure, CanMaterialize)]
478pub struct PowiOp {
479    pub lhs: Value,
480    pub rhs: Value,
481}
482
483// TODO const_eval
484
485pure_binop!("math.hypot", HypotOp);
486const_eval!(HypotOp, {
487    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs.hypot(rhs),
488});
489
490pure_binop!("math.rhypot", RhypotOp);
491const_eval!(RhypotOp, {
492    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs.hypot(rhs).recip(),
493});
494
495#[cube_op(name = "math.s_rem")]
496#[result_ty(same_as = lhs)]
497#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
498#[op_traits(CanMaterialize, NoSideEffects, NoMemoryEffect)] // Not pure because divide by zero
499pub struct SRemOp {
500    pub lhs: Value,
501    pub rhs: Value,
502}
503const_eval!(SRemOp, {
504    [IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs % rhs,
505    // 0 % x -> 0
506    custom: |lhs, _| {
507        Some(match lhs?.as_const_val(ctx) {
508            ConstantValue::Int(0) => int_attr(ctx, lhs?.get_type(ctx), 0),
509            _ => None?
510        })
511    },
512    // x % 1 -> 0
513    custom: |_, rhs| {
514        Some(match rhs?.as_const_val(ctx) {
515            ConstantValue::Int(1) => int_attr(ctx, rhs?.get_type(ctx), 0),
516            _ => None?
517        })
518    }
519});
520simplify!(SRemOp, {
521    |_, rhs| match rhs?.as_const_val(ctx) {
522        ConstantValue::Int(1) => Some(self.lhs(ctx)),
523        _ => None?,
524    }
525});
526
527#[cube_op(name = "math.u_rem")]
528#[result_ty(same_as = lhs)]
529#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
530#[op_traits(CanMaterialize, NoSideEffects, NoMemoryEffect)] // Not pure because divide by zero
531pub struct URemOp {
532    pub lhs: Value,
533    pub rhs: Value,
534}
535const_eval!(URemOp, {
536    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs % rhs,
537    // 0 % x -> 0
538    custom: |lhs, _| {
539        Some(match lhs?.as_const_val(ctx) {
540            ConstantValue::UInt(0) => int_attr(ctx, lhs?.get_type(ctx), 0),
541            _ => None?
542        })
543    },
544    // x % 1 -> 0
545    custom: |_, rhs| {
546        Some(match rhs?.as_const_val(ctx) {
547            ConstantValue::UInt(1) => int_attr(ctx, rhs?.get_type(ctx), 0),
548            _ => None?
549        })
550    }
551});
552simplify!(URemOp, {
553    |_, rhs| match rhs?.as_const_val(ctx) {
554        ConstantValue::UInt(1) => Some(self.lhs(ctx)),
555        _ => None?,
556    }
557});
558
559pure_binop!("math.f_rem", FRemOp);
560const_eval!(FRemOp, {
561    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| lhs % rhs,
562    // 0 % x -> 0
563    custom: |lhs, _| {
564        Some(match lhs?.float_as_f64(ctx) {
565            Some(0.0) => float_attr(ctx, lhs?.get_type(ctx), 0.0),
566            _ => None?
567        })
568    },
569});
570simplify!(FRemOp, {
571    |_, rhs| match rhs?.float_as_f64(ctx) {
572        Some(1.0) => Some(self.lhs(ctx)),
573        _ => None?,
574    }
575});
576
577#[cube_op(name = "math.s_mod_floor")]
578#[result_ty(same_as = lhs)]
579#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
580#[op_traits(CanMaterialize, NoSideEffects, NoMemoryEffect)] // Not pure because divide by zero
581pub struct SModFloorOp {
582    pub lhs: Value,
583    pub rhs: Value,
584}
585const_eval!(SModFloorOp, {
586    IntegerAttr(i8, i16, i32, i64): |lhs, rhs| lhs.mod_floor(&rhs),
587    // 0 % x -> 0
588    custom: |lhs, _| {
589        Some(match lhs?.as_const_val(ctx) {
590            ConstantValue::Int(0) => int_attr(ctx, lhs?.get_type(ctx), 0),
591            _ => None?
592        })
593    },
594    // x % 1 -> 0
595    custom: |_, rhs| {
596        Some(match rhs?.as_const_val(ctx) {
597            ConstantValue::Int(1) => int_attr(ctx, rhs?.get_type(ctx), 0),
598            _ => None?
599        })
600    }
601});
602
603pure_binop!("math.f_mod_floor", FModFloorOp);
604const_eval!(FModFloorOp, {
605    FloatAttr(f16, bf16, f32, f64): |lhs, rhs| lhs - (lhs / rhs).floor() * rhs,
606    // 0 % x -> 0
607    custom: |lhs, _| {
608        Some(match lhs?.float_as_f64(ctx) {
609            Some(0.0) => float_attr(ctx, lhs?.get_type(ctx), 0.0),
610            _ => None?
611        })
612    },
613});
614
615pure_binop!("math.s_mul_hi", SMulHiOp);
616const_eval!(SMulHiOp, {
617    IntegerAttr(i64): |lhs, rhs| ((lhs as i128 * rhs as i128) >> 64) as i64,
618    IntegerAttr(i32): |lhs, rhs| ((lhs as i64 * rhs as i64) >> 32) as i32,
619    // 0 * x -> 0; x * 0 -> 0
620    custom: |lhs, rhs| {
621        let const_val = lhs.or(rhs)?;
622        Some(match const_val.as_const_val(ctx) {
623            ConstantValue::Int(0) => int_attr(ctx, const_val.get_type(ctx), 0),
624            _ => None?
625        })
626    }
627});
628simplify!(SMulHiOp, {
629    |lhs, _| match lhs?.as_const_val(ctx) {
630        ConstantValue::Int(1) => Some(self.rhs(ctx)),
631        _ => None?,
632    },
633    |_, rhs| match rhs?.as_const_val(ctx) {
634        ConstantValue::Int(1) => Some(self.lhs(ctx)),
635        _ => None?,
636    }
637});
638
639pure_binop!("math.u_mul_hi", UMulHiOp);
640const_eval!(UMulHiOp, {
641    IndexAttr: |lhs, rhs| ((lhs as u128 * rhs as u128) >> 64) as usize,
642    IntegerAttr(u64): |lhs, rhs| ((lhs as u128 * rhs as u128) >> 64) as u64,
643    IntegerAttr(u32): |lhs, rhs| ((lhs as u64 * rhs as u64) >> 32) as u32,
644    // 0 * x -> 0; x * 0 -> 0
645    custom: |lhs, rhs| {
646        let const_val = lhs.or(rhs)?;
647        Some(match const_val.as_const_val(ctx) {
648            ConstantValue::UInt(0) => int_attr(ctx, const_val.get_type(ctx), 0),
649            _ => None?
650        })
651    }
652});
653simplify!(UMulHiOp, {
654    |lhs, _| match lhs?.as_const_val(ctx) {
655        ConstantValue::UInt(1) => Some(self.rhs(ctx)),
656        _ => None?,
657    },
658    |_, rhs| match rhs?.as_const_val(ctx) {
659        ConstantValue::UInt(1) => Some(self.lhs(ctx)),
660        _ => None?,
661    }
662});
663
664pub(super) fn index_attr(val: usize) -> AttrObj {
665    AttrObj::from(IndexAttr::new(val))
666}
667
668pub(super) fn int_attr(ctx: &Context, ty: TypeHandle, val: i128) -> AttrObj {
669    if ty.is_index(ctx) {
670        IndexAttr::new(val as usize).into()
671    } else {
672        let ty = TypedHandle::<IntegerType>::from_handle(ty, ctx).unwrap();
673        let width = bw(ty.deref(ctx).width() as usize);
674        let val = APInt::from_i128(val, width);
675        AttrObj::from(IntegerAttr::new(ty, val))
676    }
677}
678
679pub(super) fn float_attr(ctx: &Context, ty: TypeHandle, val: f64) -> AttrObj {
680    AttrObj::from(FloatAttr::from_f64(ctx, ty, val))
681}
682
683#[cube_op(name = "math.fma")]
684#[result_ty(same_as = a)]
685#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
686#[op_traits(Pure, CanMaterialize)]
687pub struct FmaOp {
688    pub a: Value,
689    pub b: Value,
690    pub c: Value,
691}
692const_eval!(FmaOp, {
693    FloatAttr(f16, bf16, f32, f64): |a, b, c| a * b + c,
694});