Skip to main content

cubecl_ir/dialect/
cmp.rs

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, Commutative, ConstantValue, PropagatesUniformity, Pure,
10    attributes::{BoolAttr, FloatAttr, IndexAttr, IntAttrExt},
11    dialect::{base::pure_binop, math::int_attr},
12    interfaces::{
13        Expression, ExpressionCanonicalize, ExpressionValue, TriviallyUnrollable, TypedExt,
14    },
15    prelude::*,
16    types::{VectorType, scalar::BoolType},
17};
18
19pure_binop!("cmp.s_min", SMinOp);
20Commutative!(SMinOp);
21const_eval!(SMinOp, {
22    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.min(rhs),
23    // min(min_int, x) -> min_int
24    custom: |lhs, rhs| {
25        let const_val = lhs.or(rhs)?;
26        let ty = const_val.get_type(ctx);
27        match const_val.as_const_val(ctx) {
28            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
29            _ => None
30        }
31    }
32});
33simplify!(SMinOp, {
34    // min(max_int, x) -> x
35    |lhs, _| {
36        let ty = lhs?.get_type(ctx);
37        match lhs?.as_const_val(ctx) {
38            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(self.rhs(ctx)),
39            _ => None,
40        }
41    },
42    // min(x, max_int) -> x
43    |_, rhs| {
44        let ty = rhs?.get_type(ctx);
45        match rhs?.as_const_val(ctx) {
46            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(self.lhs(ctx)),
47            _ => None,
48        }
49    },
50    // min(x, x) -> x
51    |_, _| match self.lhs(ctx) == self.rhs(ctx) {
52        true => Some(self.lhs(ctx)),
53        false => None
54    }
55});
56
57pure_binop!("cmp.u_min", UMinOp);
58Commutative!(UMinOp);
59const_eval!(UMinOp, {
60    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.min(rhs),
61    // min(min_int, x) -> min_int
62    custom: |lhs, rhs| match lhs.or(rhs)?.as_int(ctx)?.is_zero() {
63        true => Some(int_attr(ctx, self.result_type(ctx), 0)),
64        false => None,
65    }
66});
67simplify!(UMinOp, {
68    // min(max_int, x) -> x
69    |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    // min(x, max_int) -> x
77    |_, 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    // min(x, x) -> x
85    |_, _| 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);
92Commutative!(FMinOp);
93const_eval!(FMinOp, { [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| lhs.min(rhs) });
94simplify!(FMinOp, {
95    // min(x, x) -> x
96    |_, _| match self.lhs(ctx) == self.rhs(ctx) {
97        true => Some(self.lhs(ctx)),
98        false => None,
99    }
100});
101
102pure_binop!("cmp.s_max", SMaxOp);
103Commutative!(SMaxOp);
104const_eval!(SMaxOp, {
105    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| lhs.max(rhs),
106    // max(max_int, x) -> max_int
107    custom: |lhs, rhs| {
108        let const_val = lhs.or(rhs)?;
109        let ty = const_val.get_type(ctx);
110        match const_val.as_const_val(ctx) {
111            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
112            _ => None
113        }
114    }
115});
116simplify!(SMaxOp, {
117    // max(min_int, x) -> x
118    |lhs, _| {
119        let ty = lhs?.get_type(ctx);
120        match lhs?.as_const_val(ctx) {
121            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(self.rhs(ctx)),
122            _ => None,
123        }
124    },
125    // max(x, min_int) -> x
126    |_, rhs| {
127        let ty = rhs?.get_type(ctx);
128        match rhs?.as_const_val(ctx) {
129            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => Some(self.lhs(ctx)),
130            _ => None,
131        }
132    },
133    // max(x, x) -> x
134    |_, _| match self.lhs(ctx) == self.rhs(ctx) {
135        true => Some(self.lhs(ctx)),
136        false => None,
137    }
138});
139
140pure_binop!("cmp.u_max", UMaxOp);
141Commutative!(UMaxOp);
142const_eval!(UMaxOp, {
143    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| lhs.max(rhs),
144    // max(max_int, x) -> max_int
145    custom: |lhs, rhs| {
146        let const_val = lhs.or(rhs)?;
147        let ty = const_val.get_type(ctx);
148        match const_val.as_const_val(ctx) {
149            ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => Some(int_attr(ctx, ty, val as i128)),
150            _ => None
151        }
152    }
153});
154simplify!(UMaxOp, {
155    // max(min_int, x) -> x
156    |lhs, _| match lhs?.as_int(ctx)?.is_zero() {
157        true => Some(self.rhs(ctx)),
158        false => None,
159    },
160    // max(x, min_int) -> x
161    |_, rhs| match rhs?.as_int(ctx)?.is_zero() {
162        true => Some(self.lhs(ctx)),
163        false => None,
164    },
165    // max(x, x) -> x
166    |_, _| match self.lhs(ctx) == self.rhs(ctx) {
167        true => Some(self.lhs(ctx)),
168        false => None,
169    }
170});
171
172pure_binop!("cmp.f_max", FMaxOp);
173Commutative!(FMaxOp);
174const_eval!(FMaxOp, {
175    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| lhs.max(rhs),
176});
177simplify!(FMaxOp, {
178    // max(x, x) -> x
179    |_, _| 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, PropagatesUniformity)]
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    // clamp(x, max_int, y) -> max_int
197    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    // clamp(x, y, min_int) -> min_int
205    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, PropagatesUniformity)]
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    // clamp(x, max_int, y) -> max_int
226    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    // clamp(x, y, min_int) -> min_int
234    custom: |_, _, max| match max?.as_int(ctx)?.is_zero() {
235        true => Some(int_attr(ctx, self.result_type(ctx), 0)),
236        false => None
237    }
238});
239
240#[cube_op(name = "cmp.f_clamp")]
241#[result_ty(same_as = input)]
242#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
243#[op_traits(Pure, CanMaterialize, PropagatesUniformity)]
244pub struct FClampOp {
245    pub input: Value,
246    pub min: Value,
247    pub max: Value,
248}
249const_eval!(FClampOp, {
250    [FloatAttr(f16, bf16, f32, f64)]: |inp, min, max| inp.clamp(min, max),
251});
252
253/// Floating-point minimum that propagates NaN from either operand.
254/// Opposite-signed zeros may yield either sign; NaN payloads and signs are unspecified.
255#[cube_op(name = "cmp.f_min_nan")]
256#[result_ty(same_as = lhs)]
257#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
258#[op_traits(Pure, CanMaterialize, PropagatesUniformity)]
259pub struct FMinNanOp {
260    pub lhs: Value,
261    pub rhs: Value,
262}
263const_eval!(FMinNanOp, {
264    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| {
265        if lhs.is_nan() { lhs }
266        else if rhs.is_nan() { rhs }
267        else { lhs.min(rhs) }
268    },
269});
270
271/// Floating-point maximum that propagates NaN from either operand.
272/// Opposite-signed zeros may yield either sign; NaN payloads and signs are unspecified.
273#[cube_op(name = "cmp.f_max_nan")]
274#[result_ty(same_as = lhs)]
275#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
276#[op_traits(Pure, CanMaterialize, PropagatesUniformity)]
277pub struct FMaxNanOp {
278    pub lhs: Value,
279    pub rhs: Value,
280}
281const_eval!(FMaxNanOp, {
282    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| {
283        if lhs.is_nan() { lhs }
284        else if rhs.is_nan() { rhs }
285        else { lhs.max(rhs) }
286    },
287});
288
289/// Floating-point clamp that propagates NaN from any operand.
290///
291/// Applies the upper bound first, then the lower bound; reversed non-NaN bounds
292/// yield `min`. If the result is zero and the operands include zeros of opposite
293/// signs, either sign of zero may be returned. NaN payloads and signs are unspecified.
294#[cube_op(name = "cmp.f_clamp_nan")]
295#[result_ty(same_as = input)]
296#[op_interfaces(SameOperandsType, SameOperandsAndResultType, TriviallyUnrollable)]
297#[op_traits(Pure, CanMaterialize, PropagatesUniformity)]
298pub struct FClampNanOp {
299    pub input: Value,
300    pub min: Value,
301    pub max: Value,
302}
303const_eval!(FClampNanOp, {
304    [FloatAttr(f16, bf16, f32, f64)]: |inp, min, max| {
305        if inp.is_nan() { inp }
306        else if min.is_nan() { min }
307        else if max.is_nan() { max }
308        else { inp.min(max).max(min) }
309    },
310});
311
312macro_rules! cmp_binop {
313    ($name: literal, $ty: ident) => {
314        #[cubecl_macros_internal::cube_op(name = $name)]
315        #[result_ty(from_inputs = cmp_result_ty)]
316        #[$crate::prelude::op_interfaces(SameOperandsType, TriviallyUnrollable)]
317        #[op_traits(Pure, CanMaterialize, PropagatesUniformity)]
318        pub struct $ty {
319            pub lhs: Value,
320            pub rhs: Value,
321        }
322    };
323}
324
325cmp_binop!("cmp.s_less_than", SLessThanOp);
326const_eval!(SLessThanOp, {
327    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
328        (lhs < rhs).into()
329    },
330    // (x < x) -> false;
331    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
332        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
333        false => None
334    },
335    // int < min_int -> false
336    custom: |_, rhs| {
337        let ty = rhs?.get_type(ctx);
338        match rhs?.as_const_val(ctx) {
339            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
340            _ => None
341        }
342    },
343    // max_int < int -> false
344    custom: |lhs, _| {
345        let ty = lhs?.get_type(ctx);
346        match lhs?.as_const_val(ctx) {
347            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
348            _ => None
349        }
350    },
351});
352
353cmp_binop!("cmp.u_less_than", ULessThanOp);
354const_eval!(ULessThanOp, {
355    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
356        (lhs < rhs).into()
357    },
358    // (x < x) -> false;
359    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
360        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
361        false => None
362    },
363    // int < min_int -> false
364    custom: |_, rhs| match rhs?.as_int(ctx)?.is_zero() {
365        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
366        false => None
367    },
368    // max_int < int -> false
369    custom: |lhs, _| {
370        let ty = lhs?.get_type(ctx);
371        match lhs?.as_const_val(ctx) {
372            ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
373            _ => None
374        }
375    },
376});
377
378cmp_binop!("cmp.f_less_than", FLessThanOp);
379const_eval!(FLessThanOp, {
380    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
381        (lhs < rhs).into()
382    },
383});
384
385macro_rules! invert_cmp_expression {
386    ($ty: ty => $to: ty) => {
387        #[op_interface_impl]
388        impl ExpressionCanonicalize for $ty {
389            fn canonical_expression(
390                &self,
391                ctx: &Context,
392                mut operands: Vec<ExpressionValue>,
393            ) -> Expression {
394                operands.swap(0, 1);
395                Expression::new(
396                    self.result_type(ctx),
397                    <$to>::get_opid_static(),
398                    operands,
399                    Default::default(),
400                )
401            }
402        }
403    };
404}
405
406cmp_binop!("cmp.s_greater_than", SGreaterThanOp);
407invert_cmp_expression!(SGreaterThanOp => SLessThanOp);
408const_eval!(SGreaterThanOp, {
409    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
410        (lhs > rhs).into()
411    },
412    // (x > x) -> false;
413    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
414        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
415        false => None
416    },
417    // min_int > int -> false
418    custom: |lhs, _| {
419        let ty = lhs?.get_type(ctx);
420        match lhs?.as_const_val(ctx) {
421            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
422            _ => None
423        }
424    },
425    // int > max_int -> false
426    custom: |_, rhs| {
427        let ty = rhs?.get_type(ctx);
428        match rhs?.as_const_val(ctx) {
429            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
430            _ => None
431        }
432    }
433});
434
435cmp_binop!("cmp.u_greater_than", UGreaterThanOp);
436invert_cmp_expression!(UGreaterThanOp => ULessThanOp);
437const_eval!(UGreaterThanOp, {
438    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
439        (lhs > rhs).into()
440    },
441    // (x > x) -> false
442    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
443        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
444        false => None
445    },
446    // min_int > int -> false
447    custom: |lhs, _| match lhs?.as_int(ctx)?.is_zero() {
448        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
449        false => None
450    },
451    // int > max_int -> false
452    custom: |_, rhs| {
453        let ty = rhs?.get_type(ctx);
454        match rhs?.as_const_val(ctx) {
455            ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
456            _ => None
457        }
458    }
459});
460
461cmp_binop!("cmp.f_greater_than", FGreaterThanOp);
462invert_cmp_expression!(FGreaterThanOp => FLessThanOp);
463const_eval!(FGreaterThanOp, {
464    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
465        (lhs > rhs).into()
466    },
467});
468
469cmp_binop!("cmp.s_less_than_or_equal", SLessThanOrEqualOp);
470const_eval!(SLessThanOrEqualOp, {
471    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
472        (lhs <= rhs).into()
473    },
474    // (x <= x) -> true
475    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
476        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
477        false => None
478    },
479    // min_int <= int -> true
480    custom: |lhs, _| {
481        let ty = lhs?.get_type(ctx);
482        match lhs?.as_const_val(ctx) {
483            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
484            _ => None
485        }
486    },
487    // int <= max_int -> true
488    custom: |_, rhs| {
489        let ty = rhs?.get_type(ctx);
490        match rhs?.as_const_val(ctx) {
491            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
492            _ => None
493        }
494    }
495});
496
497cmp_binop!("cmp.u_less_than_or_equal", ULessThanOrEqualOp);
498const_eval!(ULessThanOrEqualOp, {
499    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
500        (lhs <= rhs).into()
501    },
502    // (x <= x) -> true
503    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
504        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
505        false => None
506    },
507    // min_int <= int -> true
508    custom: |lhs, _| match lhs?.as_int(ctx)?.is_zero() {
509        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
510        false => None
511    },
512    // int <= max_int -> true
513    custom: |_, rhs| {
514        let ty = rhs?.get_type(ctx);
515        match rhs?.as_const_val(ctx) {
516            ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
517            _ => None
518        }
519    }
520});
521
522cmp_binop!("cmp.f_less_than_or_equal", FLessThanOrEqualOp);
523const_eval!(FLessThanOrEqualOp, {
524    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
525        (lhs <= rhs).into()
526    },
527});
528
529cmp_binop!("cmp.s_greater_than_or_equal", SGreaterThanOrEqualOp);
530invert_cmp_expression!(SGreaterThanOrEqualOp => SLessThanOrEqualOp);
531const_eval!(SGreaterThanOrEqualOp, {
532    [IndexAttr, IntegerAttr(i8, i16, i32, i64)]: |lhs, rhs| -> BoolAttr {
533        (lhs >= rhs).into()
534    },
535    // (x >= x) -> true
536    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
537        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
538        false => None
539    },
540    // int >= min_int -> true
541    custom: |_, rhs| {
542        let ty = rhs?.get_type(ctx);
543        match rhs?.as_const_val(ctx) {
544            ConstantValue::Int(val) if is_min_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
545            _ => None
546        }
547    },
548    // max_int >= int -> true
549    custom: |lhs, _| {
550        let ty = lhs?.get_type(ctx);
551        match lhs?.as_const_val(ctx) {
552            ConstantValue::Int(val) if is_max_int(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
553            _ => None
554        }
555    }
556});
557
558cmp_binop!("cmp.u_greater_than_or_equal", UGreaterThanOrEqualOp);
559invert_cmp_expression!(UGreaterThanOrEqualOp => ULessThanOrEqualOp);
560const_eval!(UGreaterThanOrEqualOp, {
561    [IndexAttr, IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
562        (lhs >= rhs).into()
563    },
564    // (x >= x) -> true
565    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
566        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
567        false => None
568    },
569    // int >= min_int -> true
570    custom: |_, rhs| match rhs?.as_int(ctx)?.is_zero() {
571        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
572        false => None
573    },
574    // max_int >= int -> true
575    custom: |lhs, _| {
576        let ty = lhs?.get_type(ctx);
577        match lhs?.as_const_val(ctx) {
578            ConstantValue::UInt(val) if is_max_uint(ctx, ty, val) => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
579            _ => None
580        }
581    }
582});
583
584cmp_binop!("cmp.f_greater_than_or_equal", FGreaterThanOrEqualOp);
585invert_cmp_expression!(FGreaterThanOrEqualOp => FLessThanOrEqualOp);
586const_eval!(FGreaterThanOrEqualOp, {
587    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
588        (lhs >= rhs).into()
589    },
590});
591
592cmp_binop!("cmp.i_equal", IEqualOp);
593Commutative!(IEqualOp);
594const_eval!(IEqualOp, {
595    [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
596        (lhs == rhs).into()
597    },
598    // (x == x) -> true
599    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
600        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
601        false => None
602    }
603});
604
605cmp_binop!("cmp.f_equal", FEqualOp);
606Commutative!(FEqualOp);
607const_eval!(FEqualOp, {
608    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
609        (lhs == rhs).into()
610    },
611});
612
613cmp_binop!("cmp.bool_equal", BoolEqualOp);
614Commutative!(BoolEqualOp);
615const_eval!(BoolEqualOp, {
616    [BoolAttr]: |lhs, rhs| lhs == rhs,
617    // (x == x) -> true
618    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
619        true => BoolAttr::per_lane(ctx, self.get_result(ctx), true),
620        false => None
621    }
622});
623
624cmp_binop!("cmp.i_not_equal", INotEqualOp);
625Commutative!(INotEqualOp);
626const_eval!(INotEqualOp, {
627    [IndexAttr, IntegerAttr(i8, i16, i32, i64), IntegerAttr(u8, u16, u32, u64)]: |lhs, rhs| -> BoolAttr {
628        (lhs != rhs).into()
629    },
630    // (x != x) == false
631    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
632        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
633        false => None
634    },
635});
636
637cmp_binop!("cmp.f_not_equal", FNotEqualOp);
638Commutative!(FNotEqualOp);
639const_eval!(FNotEqualOp, {
640    [FloatAttr(f16, bf16, f32, f64)]: |lhs, rhs| -> BoolAttr {
641        (lhs != rhs).into()
642    },
643});
644
645cmp_binop!("cmp.bool_not_equal", BoolNotEqualOp);
646Commutative!(BoolNotEqualOp);
647const_eval!(BoolNotEqualOp, {
648    [BoolAttr]: |lhs, rhs| lhs != rhs,
649    // (x != x) == false
650    custom: |_, _| match self.lhs(ctx) == self.rhs(ctx) {
651        true => BoolAttr::per_lane(ctx, self.get_result(ctx), false),
652        false => None
653    }
654});
655
656fn cmp_result_ty(ctx: &Context, lhs: &Value, _: &Value) -> TypeHandle {
657    let vectorization = lhs.vector_size(ctx);
658    let bool = BoolType::get(ctx).into();
659    if vectorization == 1 {
660        bool
661    } else {
662        VectorType::get(ctx, bool, vectorization).into()
663    }
664}
665
666pub(super) fn width(ctx: &Context, ty: TypeHandle) -> usize {
667    ty.size(ctx) * 8
668}
669
670fn is_min_int(ctx: &Context, ty: TypeHandle, val: i64) -> bool {
671    let ty = TypedHandle::<IntegerType>::from_handle(ty, ctx).unwrap();
672    val == min_int(ty.deref(ctx).width() as usize)
673}
674
675pub(super) fn is_max_int(ctx: &Context, ty: TypeHandle, val: i64) -> bool {
676    let ty = TypedHandle::<IntegerType>::from_handle(ty, ctx).unwrap();
677    val == max_int(ty.deref(ctx).width() as usize)
678}
679
680pub(super) fn is_max_uint(ctx: &Context, ty: TypeHandle, val: u64) -> bool {
681    val == max_uint(ty.size_bits(ctx))
682}
683
684fn min_int(width: usize) -> i64 {
685    if width >= 64 {
686        i64::MIN
687    } else {
688        -(1i64 << (width - 1))
689    }
690}
691
692fn max_int(width: usize) -> i64 {
693    if width >= 64 {
694        i64::MAX
695    } else {
696        (1i64 << (width - 1)) - 1
697    }
698}
699
700fn max_uint(width: usize) -> u64 {
701    if width >= 64 {
702        u64::MAX
703    } else {
704        (1u64 << width) - 1
705    }
706}
707
708#[cfg(test)]
709mod clamp_nan_tests {
710    use super::*;
711    use crate::types::scalar::Float32Type;
712    use alloc::boxed::Box;
713    use pliron::{
714        attribute::AttrObj, builtin::ops::ConstantOp, opts::constants::ConstFoldInterface,
715    };
716
717    fn constants<const N: usize>(
718        ctx: &mut Context,
719        values: [f64; N],
720    ) -> ([Value; N], [Option<AttrObj>; N]) {
721        let ty = Float32Type::get(ctx).to_handle();
722        let attrs = values.map(|v| FloatAttr::from_f64(ctx, ty, v));
723        let operands = attrs
724            .each_ref()
725            .map(|attr| ConstantOp::new(ctx, Box::new(attr.clone())).get_result(ctx));
726        (operands, attrs.map(|attr| Some(Box::new(attr) as AttrObj)))
727    }
728
729    fn assert_folded(ctx: &Context, folded: &[Option<AttrObj>], expected: f64) {
730        let actual = folded[0]
731            .as_ref()
732            .unwrap()
733            .downcast_ref::<FloatAttr>()
734            .unwrap();
735        let actual = actual.float_type(ctx).value_to_f64(actual.val);
736        assert!(
737            actual == expected || (actual.is_nan() && expected.is_nan()),
738            "expected {expected}, got {actual}"
739        );
740    }
741
742    #[test]
743    fn min_max_nan_constant_folding() {
744        let mut ctx = Context::default();
745        for (lhs, rhs, expected_min, expected_max) in [
746            (f64::NAN, 3.0, f64::NAN, f64::NAN),
747            (3.0, f64::NAN, f64::NAN, f64::NAN),
748            (f64::NAN, f64::NAN, f64::NAN, f64::NAN),
749            (-2.0, 3.0, -2.0, 3.0),
750        ] {
751            let ([lhs, rhs], attrs) = constants(&mut ctx, [lhs, rhs]);
752            let min = FMinNanOp::new(&mut ctx, lhs, rhs).check_fold(&ctx, &attrs);
753            let max = FMaxNanOp::new(&mut ctx, lhs, rhs).check_fold(&ctx, &attrs);
754            assert_folded(&ctx, &min, expected_min);
755            assert_folded(&ctx, &max, expected_max);
756        }
757    }
758
759    #[test]
760    fn clamp_nan_constant_folding() {
761        let mut ctx = Context::default();
762        for (input, min, max, expected) in [
763            (f64::NAN, 0.0, 1.0, f64::NAN),
764            (0.5, f64::NAN, 1.0, f64::NAN),
765            (0.5, 0.0, f64::NAN, f64::NAN),
766            (2.0, 0.0, 1.0, 1.0),
767        ] {
768            let ([input, min, max], attrs) = constants(&mut ctx, [input, min, max]);
769            let folded = FClampNanOp::new(&mut ctx, input, min, max).check_fold(&ctx, &attrs);
770            assert_folded(&ctx, &folded, expected);
771        }
772    }
773}