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