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