1use cubecl_macros_internal::{const_eval, cube_op, simplify};
2use half::{bf16, f16};
3use pliron::{
4 builtin::{attributes::IntegerAttr, types::IntegerType},
5 r#type::TypeHandle,
6};
7
8use crate::{
9 CanMaterialize, ConstantValue, Pure,
10 attributes::{BoolAttr, FloatAttr, IndexAttr, IntAttrExt},
11 dialect::{base::pure_binop, math::int_attr},
12 interfaces::{TriviallyUnrollable, TypedExt},
13 prelude::*,
14 types::{VectorType, scalar::BoolType},
15};
16
17pure_binop!("cmp.s_min", SMinOp);
18const_eval!(SMinOp, {
19 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.min(rhs),
20 custom: |lhs, rhs| {
22 let const_val = lhs.or(rhs)?;
23 let ty = const_val.get_type(ctx);
24 match const_val.as_const_val(ctx) {
25 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
26 _ => None
27 }
28 }
29});
30simplify!(SMinOp, {
31 |lhs, _| {
33 let ty = lhs?.get_type(ctx);
34 match lhs?.as_const_val(ctx) {
35 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(self.rhs(ctx)),
36 _ => None,
37 }
38 },
39 |_, rhs| {
41 let ty = rhs?.get_type(ctx);
42 match rhs?.as_const_val(ctx) {
43 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(self.lhs(ctx)),
44 _ => None,
45 }
46 },
47 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
49 true => Some(self.lhs(ctx)),
50 false => None
51 }
52});
53
54pure_binop!("cmp.u_min", UMinOp);
55const_eval!(UMinOp, {
56 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.min(rhs),
57 custom: |lhs, rhs| {
59 let const_val = lhs.or(rhs)?;
60 let ty = const_val.get_type(ctx);
61 match const_val.as_const_val(ctx) {
62 ConstantValue::UInt(0) => Some(int_attr(ctx, ty, 0)),
63 _ => None
64 }
65 }
66});
67simplify!(UMinOp, {
68 |lhs, _| {
70 let ty = lhs?.get_type(ctx);
71 match lhs?.as_const_val(ctx) {
72 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => Some(self.rhs(ctx)),
73 _ => None,
74 }
75 },
76 |_, rhs| {
78 let ty = rhs?.get_type(ctx);
79 match rhs?.as_const_val(ctx) {
80 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => Some(self.lhs(ctx)),
81 _ => None,
82 }
83 },
84 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
86 true => Some(self.lhs(ctx)),
87 false => None
88 }
89});
90
91pure_binop!("cmp.f_min", FMinOp);
92const_eval!(FMinOp, { [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| lhs.min(rhs) });
93simplify!(FMinOp, {
94 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
96 true => Some(self.lhs(ctx)),
97 false => None,
98 }
99});
100
101pure_binop!("cmp.s_max", SMaxOp);
102const_eval!(SMaxOp, {
103 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.max(rhs),
104 custom: |lhs, rhs| {
106 let const_val = lhs.or(rhs)?;
107 let ty = const_val.get_type(ctx);
108 match const_val.as_const_val(ctx) {
109 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
110 _ => None
111 }
112 }
113});
114simplify!(SMaxOp, {
115 |lhs, _| {
117 let ty = lhs?.get_type(ctx);
118 match lhs?.as_const_val(ctx) {
119 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(self.rhs(ctx)),
120 _ => None,
121 }
122 },
123 |_, rhs| {
125 let ty = rhs?.get_type(ctx);
126 match rhs?.as_const_val(ctx) {
127 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(self.lhs(ctx)),
128 _ => None,
129 }
130 },
131 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
133 true => Some(self.lhs(ctx)),
134 false => None,
135 }
136});
137
138pure_binop!("cmp.u_max", UMaxOp);
139const_eval!(UMaxOp, {
140 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.max(rhs),
141 custom: |lhs, rhs| {
143 let const_val = lhs.or(rhs)?;
144 let ty = const_val.get_type(ctx);
145 match const_val.as_const_val(ctx) {
146 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
147 _ => None
148 }
149 }
150});
151simplify!(UMaxOp, {
152 |lhs, _| {
154 match lhs?.as_const_val(ctx) {
155 ConstantValue::UInt(0) => Some(self.rhs(ctx)),
156 _ => None,
157 }
158 },
159 |_, rhs| {
161 match rhs?.as_const_val(ctx) {
162 ConstantValue::UInt(0) => Some(self.lhs(ctx)),
163 _ => None,
164 }
165 },
166 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
168 true => Some(self.lhs(ctx)),
169 false => None,
170 }
171});
172
173pure_binop!("cmp.f_max", FMaxOp);
174const_eval!(FMaxOp, {
175 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| lhs.max(rhs),
176});
177simplify!(FMaxOp, {
178 |_, _| match self.lhs(ctx) == self.rhs(ctx) {
180 true => Some(self.lhs(ctx)),
181 false => None,
182 }
183});
184
185#[cube_op(name = "cmp.s_clamp")]
186#[result_ty(same_as = input)]
187#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
188#[op_traits(Pure, CanMaterialize)]
189pub struct SClampOp {
190 pub input: Value,
191 pub min: Value,
192 pub max: Value,
193}
194const_eval!(SClampOp, {
195 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |inp, min, max| inp.clamp(min, max),
196 custom: |_, min, _| {
198 let ty = min?.get_type(ctx);
199 match min?.as_const_val(ctx) {
200 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
201 _ => None
202 }
203 },
204 custom: |_, _, max| {
206 let ty = max?.get_type(ctx);
207 match max?.as_const_val(ctx) {
208 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
209 _ => None
210 }
211 }
212});
213
214#[cube_op(name = "cmp.u_clamp")]
215#[result_ty(same_as = input)]
216#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
217#[op_traits(Pure, CanMaterialize)]
218pub struct UClampOp {
219 pub input: Value,
220 pub min: Value,
221 pub max: Value,
222}
223const_eval!(UClampOp, {
224 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |inp, min, max| inp.clamp(min, max),
225 custom: |_, min, _| {
227 let ty = min?.get_type(ctx);
228 match min?.as_const_val(ctx) {
229 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
230 _ => None
231 }
232 },
233 custom: |_, _, max| {
235 let ty = max?.get_type(ctx);
236 match max?.as_const_val(ctx) {
237 ConstantValue::UInt(0) => Some(int_attr(ctx, ty, 0)),
238 _ => None
239 }
240 }
241});
242
243#[cube_op(name = "cmp.f_clamp")]
244#[result_ty(same_as = input)]
245#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
246#[op_traits(Pure, CanMaterialize)]
247pub struct FClampOp {
248 pub input: Value,
249 pub min: Value,
250 pub max: Value,
251}
252const_eval!(FClampOp, {
253 [FloatAttr(f16, bf16, f32, f64)]: |inp, min, max| inp.clamp(min, max),
254});
255
256macro_rules! cmp_binop {
257 ($name: literal, $ty: ident) => {
258 #[cubecl_macros_internal::cube_op(name = $name)]
259 #[result_ty(from_inputs = cmp_result_ty)]
260 #[$crate::prelude::op_interfaces(SameOperandsType, TriviallyUnrollable)]
261 #[op_traits(Pure, CanMaterialize)]
262 pub struct $ty {
263 pub lhs: Value,
264 pub rhs: Value,
265 }
266 };
267}
268
269cmp_binop!("cmp.s_less_than", SLessThanOp);
270const_eval!(SLessThanOp, {
271 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
272 (lhs < rhs).into()
273 },
274 custom: |_, _| {
276 if self.lhs(ctx) == self.rhs(ctx) {
277 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
278 } else {
279 None
280 }
281 },
282 custom: |_, rhs| {
284 let ty = rhs?.get_type(ctx);
285 match rhs?.as_const_val(ctx) {
286 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
287 _ => None
288 }
289 },
290 custom: |lhs, _| {
292 let ty = lhs?.get_type(ctx);
293 match lhs?.as_const_val(ctx) {
294 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
295 _ => None
296 }
297 },
298});
299
300cmp_binop!("cmp.u_less_than", ULessThanOp);
301const_eval!(ULessThanOp, {
302 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
303 (lhs < rhs).into()
304 },
305 custom: |_, _| {
307 if self.lhs(ctx) == self.rhs(ctx) {
308 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
309 } else {
310 None
311 }
312 },
313 custom: |_, rhs| {
315 match rhs?.as_const_val(ctx) {
316 ConstantValue::UInt(0) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
317 _ => None
318 }
319 },
320 custom: |lhs, _| {
322 let ty = lhs?.get_type(ctx);
323 match lhs?.as_const_val(ctx) {
324 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
325 _ => None
326 }
327 },
328});
329
330cmp_binop!("cmp.f_less_than", FLessThanOp);
331const_eval!(FLessThanOp, {
332 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
333 (lhs < rhs).into()
334 },
335});
336
337cmp_binop!("cmp.s_greater_than", SGreaterThanOp);
338const_eval!(SGreaterThanOp, {
339 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
340 (lhs > rhs).into()
341 },
342 custom: |_, _| {
344 if self.lhs(ctx) == self.rhs(ctx) {
345 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
346 } else {
347 None
348 }
349 },
350 custom: |lhs, _| {
352 let ty = lhs?.get_type(ctx);
353 match lhs?.as_const_val(ctx) {
354 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
355 _ => None
356 }
357 },
358 custom: |_, rhs| {
360 let ty = rhs?.get_type(ctx);
361 match rhs?.as_const_val(ctx) {
362 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
363 _ => None
364 }
365 }
366});
367
368cmp_binop!("cmp.u_greater_than", UGreaterThanOp);
369const_eval!(UGreaterThanOp, {
370 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
371 (lhs > rhs).into()
372 },
373 custom: |_, _| {
375 if self.lhs(ctx) == self.rhs(ctx) {
376 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
377 } else {
378 None
379 }
380 },
381 custom: |lhs, _| {
383 match lhs?.as_const_val(ctx) {
384 ConstantValue::UInt(0) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
385 _ => None
386 }
387 },
388 custom: |_, rhs| {
390 let ty = rhs?.get_type(ctx);
391 match rhs?.as_const_val(ctx) {
392 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
393 _ => None
394 }
395 }
396});
397
398cmp_binop!("cmp.f_greater_than", FGreaterThanOp);
399const_eval!(FGreaterThanOp, {
400 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
401 (lhs > rhs).into()
402 },
403});
404
405cmp_binop!("cmp.s_less_than_or_equal", SLessThanOrEqualOp);
406const_eval!(SLessThanOrEqualOp, {
407 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
408 (lhs <= rhs).into()
409 },
410 custom: |_, _| {
412 if self.lhs(ctx) == self.rhs(ctx) {
413 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
414 } else {
415 None
416 }
417 },
418 custom: |lhs, _| {
420 let ty = lhs?.get_type(ctx);
421 match lhs?.as_const_val(ctx) {
422 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
423 _ => None
424 }
425 },
426 custom: |_, rhs| {
428 let ty = rhs?.get_type(ctx);
429 match rhs?.as_const_val(ctx) {
430 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
431 _ => None
432 }
433 }
434});
435
436cmp_binop!("cmp.u_less_than_or_equal", ULessThanOrEqualOp);
437const_eval!(ULessThanOrEqualOp, {
438 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
439 (lhs <= rhs).into()
440 },
441 custom: |_, _| {
443 if self.lhs(ctx) == self.rhs(ctx) {
444 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
445 } else {
446 None
447 }
448 },
449 custom: |lhs, _| {
451 match lhs?.as_const_val(ctx) {
452 ConstantValue::UInt(0) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
453 _ => None
454 }
455 },
456 custom: |_, rhs| {
458 let ty = rhs?.get_type(ctx);
459 match rhs?.as_const_val(ctx) {
460 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
461 _ => None
462 }
463 }
464});
465
466cmp_binop!("cmp.f_less_than_or_equal", FLessThanOrEqualOp);
467const_eval!(FLessThanOrEqualOp, {
468 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
469 (lhs <= rhs).into()
470 },
471});
472
473cmp_binop!("cmp.s_greater_than_or_equal", SGreaterThanOrEqualOp);
474const_eval!(SGreaterThanOrEqualOp, {
475 [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
476 (lhs >= rhs).into()
477 },
478 custom: |_, _| {
480 if self.lhs(ctx) == self.rhs(ctx) {
481 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
482 } else {
483 None
484 }
485 },
486 custom: |_, rhs| {
488 let ty = rhs?.get_type(ctx);
489 match rhs?.as_const_val(ctx) {
490 ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
491 _ => None
492 }
493 },
494 custom: |lhs, _| {
496 let ty = lhs?.get_type(ctx);
497 match lhs?.as_const_val(ctx) {
498 ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
499 _ => None
500 }
501 }
502});
503
504cmp_binop!("cmp.u_greater_than_or_equal", UGreaterThanOrEqualOp);
505const_eval!(UGreaterThanOrEqualOp, {
506 [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
507 (lhs >= rhs).into()
508 },
509 custom: |_, _| {
511 if self.lhs(ctx) == self.rhs(ctx) {
512 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
513 } else {
514 None
515 }
516 },
517 custom: |_, rhs| {
519 match rhs?.as_const_val(ctx) {
520 ConstantValue::UInt(0) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
521 _ => None
522 }
523 },
524 custom: |lhs, _| {
526 let ty = lhs?.get_type(ctx);
527 match lhs?.as_const_val(ctx) {
528 ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
529 _ => None
530 }
531 }
532});
533
534cmp_binop!("cmp.f_greater_than_or_equal", FGreaterThanOrEqualOp);
535const_eval!(FGreaterThanOrEqualOp, {
536 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
537 (lhs >= rhs).into()
538 },
539});
540
541cmp_binop!("cmp.i_equal", IEqualOp);
542const_eval!(IEqualOp, {
543 [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
544 (lhs == rhs).into()
545 },
546 custom: |_, _| {
548 if self.lhs(ctx) == self.rhs(ctx) {
549 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
550 } else {
551 None
552 }
553 }
554});
555
556cmp_binop!("cmp.f_equal", FEqualOp);
557const_eval!(FEqualOp, {
558 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
559 (lhs == rhs).into()
560 },
561});
562
563cmp_binop!("cmp.bool_equal", BoolEqualOp);
564const_eval!(BoolEqualOp, {
565 [BoolAttr]: |lhs, rhs| lhs == rhs,
566 custom: |_, _| {
568 if self.lhs(ctx) == self.rhs(ctx) {
569 BoolAttr::per_lane(ctx, self.get_result(ctx), true)
570 } else {
571 None
572 }
573 }
574});
575
576cmp_binop!("cmp.i_not_equal", INotEqualOp);
577const_eval!(INotEqualOp, {
578 [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
579 (lhs != rhs).into()
580 },
581 custom: |_, _| {
583 if self.lhs(ctx) == self.rhs(ctx) {
584 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
585 } else {
586 None
587 }
588 }
589});
590
591cmp_binop!("cmp.f_not_equal", FNotEqualOp);
592const_eval!(FNotEqualOp, {
593 [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
594 (lhs != rhs).into()
595 },
596});
597
598cmp_binop!("cmp.bool_not_equal", BoolNotEqualOp);
599const_eval!(BoolNotEqualOp, {
600 [BoolAttr]: |lhs, rhs| lhs != rhs,
601 custom: |_, _| {
603 if self.lhs(ctx) == self.rhs(ctx) {
604 BoolAttr::per_lane(ctx, self.get_result(ctx), false)
605 } else {
606 None
607 }
608 }
609});
610
611fn cmp_result_ty(ctx: &Context, lhs: &Value, _: &Value) -> TypeHandle {
612 let vectorization = lhs.vector_size(ctx);
613 let bool = BoolType::get(ctx).into();
614 if vectorization == 1 {
615 bool
616 } else {
617 VectorType::get(ctx, bool, vectorization).into()
618 }
619}
620
621pub(super) fn width(ctx: &Context, ty: TypeHandle) -> usize {
622 ty.size(ctx) * 8
623}
624
625fn is_min_int(ctx: &Context, ty: TypeHandle, val: i64) -> bool {
626 let ty = TypedHandle::<IntegerType>::from_handle(ty, ctx).unwrap();
627 val == min_int(ty.deref(ctx).width() as usize)
628}
629
630pub(super) fn is_max_int(ctx: &Context, ty: TypeHandle, val: i64) -> bool {
631 let ty = TypedHandle::<IntegerType>::from_handle(ty, ctx).unwrap();
632 val == max_int(ty.deref(ctx).width() as usize)
633}
634
635pub(super) fn is_max_uint(ctx: &Context, ty: TypeHandle, val: u64) -> bool {
636 val == max_uint(ty.size_bits(ctx))
637}
638
639fn min_int(width: usize) -> i64 {
640 if width >= 64 {
641 i64::MIN
642 } else {
643 -(1i64 << (width - 1))
644 }
645}
646
647fn max_int(width: usize) -> i64 {
648 if width >= 64 {
649 i64::MAX
650 } else {
651 (1i64 << (width - 1)) - 1
652 }
653}
654
655fn max_uint(width: usize) -> u64 {
656 if width >= 64 {
657 u64::MAX
658 } else {
659 (1u64 << width) - 1
660 }
661}