Skip to main content

cubecl_ir/interfaces/
mod.rs

1use crate::{
2    ConstantValue, ElemType,
3    dialect::synchronization::SyncScope,
4    prelude::*,
5    types::{AtomicType, PointerType, VectorType, scalar::*},
6};
7use pliron::{
8    alloc::vec::Vec,
9    attribute::{AttrObj, AttributeDict},
10    builtin::{attr_interfaces::TypedAttrInterface, ops::ConstantOp, types::IntegerType},
11    context::Context,
12    derive::{op_interface, type_interface},
13    opts::dce::SideEffects,
14    r#type::{TypeHandle, type_cast},
15    value::Use,
16};
17
18pub mod aliasing;
19pub mod memory_slot;
20
21#[macro_export]
22macro_rules! verify_op_succ {
23    () => {
24        fn verify(
25            _op: &dyn pliron::op::Op,
26            _ctx: &pliron::context::Context,
27        ) -> pliron::result::Result<()>
28        where
29            Self: Sized,
30        {
31            Ok(())
32        }
33    };
34}
35
36#[macro_export]
37macro_rules! verify_ty_succ {
38    () => {
39        fn verify(
40            _op: &dyn pliron::r#type::Type,
41            _ctx: &pliron::context::Context,
42        ) -> pliron::result::Result<()>
43        where
44            Self: Sized,
45        {
46            Ok(())
47        }
48    };
49}
50
51#[macro_export]
52macro_rules! verify_attr_succ {
53    () => {
54        fn verify(
55            _op: &dyn pliron::attribute::Attribute,
56            _ctx: &pliron::context::Context,
57        ) -> pliron::result::Result<()>
58        where
59            Self: Sized,
60        {
61            Ok(())
62        }
63    };
64}
65
66#[macro_export]
67macro_rules! Pure {
68    ($ty: ty) => {
69        $crate::NoSideEffects!($ty);
70        $crate::NoMemoryEffect!($ty);
71    };
72}
73
74#[op_interface]
75pub trait TriviallyUnrollable: MaterializableOp {
76    verify_op_succ!();
77}
78
79/// Op that can be rematerialized from a set of operands.
80/// Should be implemented for anything that doesn't have regions or successors.
81#[op_interface]
82pub trait MaterializableOp {
83    verify_op_succ!();
84    fn materialize(
85        &self,
86        ctx: &mut Context,
87        result_ty: Vec<TypeHandle>,
88        operands: Vec<Value>,
89        attributes: AttributeDict,
90    ) -> Ptr<Operation>;
91}
92
93#[macro_export]
94macro_rules! CanMaterialize {
95    ($ty: ty) => {
96        #[::pliron::derive::op_interface_impl]
97        impl $crate::interfaces::MaterializableOp for $ty {
98            fn materialize(
99                &self,
100                ctx: &mut pliron::context::Context,
101                result_ty: Vec<pliron::r#type::TypeHandle>,
102                operands: Vec<Value>,
103                attributes: pliron::attribute::AttributeDict,
104            ) -> pliron::context::Ptr<pliron::operation::Operation> {
105                use pliron::op::Op;
106                let op = pliron::operation::Operation::new(
107                    ctx,
108                    Self::get_concrete_op_info(),
109                    result_ty,
110                    operands,
111                    vec![],
112                    0,
113                );
114                op.deref_mut(ctx).attributes = attributes;
115                op
116            }
117        }
118    };
119}
120
121CanMaterialize!(ConstantOp);
122
123#[op_interface]
124pub trait Synchronizes: SideEffects {
125    verify_op_succ!();
126
127    /// Synchronizes at least at this scope. Should be used for optimizations where smaller scopes
128    /// are more conservative (i.e. marking shared memory as unused).
129    fn minimum_scope(&self, ctx: &Context) -> SyncScope;
130    /// Synchronizes at most at this scope. Should be used for optimizations where larger scopes
131    /// are more conservative (i.e. making memory writes visible).
132    fn maximum_scope(&self, ctx: &Context) -> SyncScope;
133}
134
135macro_rules! synchronizes {
136    ($ty: ty, $scope: expr) => {
137        #[::pliron::derive::op_interface_impl]
138        impl crate::interfaces::Synchronizes for $ty {
139            #[allow(unused_variables)]
140            fn minimum_scope(&self, ctx: &::pliron::context::Context) -> SyncScope {
141                $scope
142            }
143            #[allow(unused_variables)]
144            fn maximum_scope(&self, ctx: &::pliron::context::Context) -> SyncScope {
145                $scope
146            }
147        }
148        #[pliron::derive::op_interface_impl]
149        impl pliron::opts::dce::SideEffects for $ty {
150            fn has_side_effects(&self, _ctx: &Context) -> bool {
151                true
152            }
153        }
154    };
155}
156pub(crate) use synchronizes;
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
159pub enum MemoryEffect {
160    Read(Value),
161    Write(Value),
162    ReadAll,
163    WriteAll,
164}
165
166#[op_interface]
167pub trait MemoryEffects {
168    verify_op_succ!();
169    fn memory_effects(&self, ctx: &Context) -> Vec<MemoryEffect>;
170}
171
172#[macro_export]
173macro_rules! NoMemoryEffect {
174    ($ty: ty) => {
175        #[::pliron::derive::op_interface_impl]
176        impl $crate::interfaces::MemoryEffects for $ty {
177            fn memory_effects(
178                &self,
179                _ctx: &pliron::context::Context,
180            ) -> $crate::alloc::vec::Vec<$crate::interfaces::MemoryEffect> {
181                $crate::alloc::vec![]
182            }
183        }
184    };
185}
186
187NoMemoryEffect!(ConstantOp);
188
189#[type_interface]
190pub trait AlignedType {
191    verify_ty_succ!();
192
193    fn align(&self, ctx: &Context) -> usize;
194}
195
196#[macro_export]
197macro_rules! aligned {
198    ($ty: ty, $align: expr) => {
199        #[::pliron::derive::type_interface_impl]
200        impl $crate::interfaces::AlignedType for $ty {
201            #[allow(unused_variables)]
202            fn align(&self, ctx: &::pliron::context::Context) -> usize {
203                $align
204            }
205        }
206    };
207}
208
209#[type_interface]
210pub trait SizedType: AlignedType {
211    verify_ty_succ!();
212    fn size(&self, ctx: &Context) -> usize;
213    fn size_bits(&self, ctx: &Context) -> usize {
214        self.size(ctx) * 8
215    }
216}
217
218#[macro_export]
219macro_rules! sized {
220    ($ty: ty, $size: expr) => {
221        #[::pliron::derive::type_interface_impl]
222        impl $crate::interfaces::SizedType for $ty {
223            #[allow(unused_variables)]
224            fn size(&self, ctx: &::pliron::context::Context) -> usize {
225                $size
226            }
227        }
228    };
229}
230
231#[macro_export]
232macro_rules! HasSideEffects {
233    ($ty: ty) => {
234        #[::pliron::derive::op_interface_impl]
235        impl pliron::opts::dce::SideEffects for $ty {
236            fn has_side_effects(&self, _ctx: &pliron::context::Context) -> bool {
237                true
238            }
239        }
240    };
241}
242
243#[macro_export]
244macro_rules! NoSideEffects {
245    ($ty: ty) => {
246        #[::pliron::derive::op_interface_impl]
247        impl pliron::opts::dce::SideEffects for $ty {
248            fn has_side_effects(&self, _ctx: &pliron::context::Context) -> bool {
249                false
250            }
251        }
252    };
253}
254
255#[type_interface]
256pub trait MaybeVectorizedType {
257    verify_ty_succ!();
258
259    fn vector_size(&self, ctx: &Context) -> usize;
260    fn try_vector_size(&self, ctx: &Context) -> Option<usize> {
261        Some(self.vector_size(ctx))
262    }
263}
264
265#[macro_export]
266macro_rules! scalar {
267    ($ty: ty) => {
268        #[::pliron::derive::type_interface_impl]
269        impl $crate::interfaces::MaybeVectorizedType for $ty {
270            fn vector_size(&self, _ctx: &::pliron::context::Context) -> usize {
271                1
272            }
273        }
274
275        #[::pliron::derive::type_interface_impl]
276        impl $crate::interfaces::ScalarizableType for $ty {
277            fn scalar_type(&self, ctx: &Context) -> ::pliron::r#type::TypeHandle {
278                use ::pliron::r#type::Type;
279                self.get_self_handle(ctx)
280            }
281        }
282
283        #[::pliron::derive::type_interface_impl]
284        impl $crate::interfaces::HasElementType for $ty {
285            fn element_type(&self, ctx: &Context) -> Option<::pliron::r#type::TypeHandle> {
286                use ::pliron::r#type::Type;
287                Some(self.get_self_handle(ctx))
288            }
289        }
290    };
291}
292
293#[type_interface]
294pub trait MaybePackedType {
295    verify_ty_succ!();
296
297    fn packing_factor(&self, ctx: &Context) -> usize;
298}
299
300macro_rules! not_packed {
301    ($ty: ty) => {
302        #[::pliron::derive::type_interface_impl]
303        impl crate::interfaces::MaybePackedType for $ty {
304            fn packing_factor(&self, _ctx: &::pliron::context::Context) -> usize {
305                1
306            }
307        }
308    };
309}
310pub(crate) use not_packed;
311
312#[type_interface]
313pub trait ScalarizableType {
314    verify_ty_succ!();
315    fn scalar_type(&self, ctx: &Context) -> TypeHandle;
316}
317
318#[type_interface]
319pub trait ScalarType {
320    verify_ty_succ!();
321    fn elem_type(&self, ctx: &Context) -> ElemType;
322}
323
324#[type_interface]
325pub trait IndexableType {
326    verify_ty_succ!();
327
328    fn indexed_type(&self, ctx: &Context) -> TypeHandle;
329}
330
331#[type_interface]
332pub trait HasElementType {
333    verify_ty_succ!();
334    fn element_type(&self, ctx: &Context) -> Option<TypeHandle>;
335}
336
337#[op_interface]
338pub trait SimplifyInterface {
339    verify_op_succ!();
340    fn check_fold(&self, ctx: &Context, operand_attrs: &[Option<AttrObj>]) -> Option<Value>;
341}
342
343#[op_interface]
344pub trait CanonicalizeInterface {
345    verify_op_succ!();
346    fn canonicalize(&self, ctx: &mut Context, rewriter: &mut MatchRewriter) -> Result<()>;
347}
348
349#[attr_interface]
350pub trait ConstantAttr: TypedAttrInterface {
351    verify_attr_succ!();
352    fn as_const_val(&self, ctx: &Context) -> ConstantValue;
353    fn float_as_f64(&self, _ctx: &Context) -> Option<f64> {
354        None
355    }
356}
357
358#[macro_export]
359macro_rules! try_cast_ty {
360    ($ty: expr, $ctx: expr, $interface: ty) => {
361        $crate::prelude::type_cast::<$interface>(&*$ty)
362            .ok_or_else(|| {
363                $crate::alloc::format!(
364                    "Expected type {} {} to implement {}",
365                    $ty.get_type_id(),
366                    $ty.disp($ctx),
367                    stringify!($interface)
368                )
369            })
370            .unwrap()
371    };
372}
373
374#[macro_export]
375macro_rules! try_cast_op {
376    ($op: expr, $ctx: expr, $interface: ty) => {
377        $crate::prelude::op_cast::<$interface>(&*$op)
378            .ok_or_else(|| {
379                $crate::alloc::format!(
380                    "Expected op {} {} to implement {}",
381                    $op.get_opid(),
382                    $op.disp($ctx),
383                    stringify!($interface)
384                )
385            })
386            .unwrap()
387    };
388}
389
390#[macro_export]
391macro_rules! match_ty {
392    (($handle: expr) { $($ty: ty => $body: expr,)*; _ => $default: expr }) => {
393        (|| {
394            $(if $handle.is::<$ty>() {
395                return $body;
396            })*
397            $default
398        })()
399    };
400    (($handle: expr) { $($ty: ty => $body: expr,)* }) => {
401        (|| {
402            $(if $handle.is::<$ty>() {
403                return $body;
404            })*
405            unreachable!()
406        })()
407    };
408}
409
410pub trait TypedExt: Typed {
411    fn size(&self, ctx: &Context) -> usize {
412        let ty = self.get_type(ctx).deref(ctx);
413        let sized = try_cast_ty!(ty, ctx, dyn SizedType);
414        sized.size(ctx)
415    }
416
417    fn size_bits(&self, ctx: &Context) -> usize {
418        let ty = self.get_type(ctx).deref(ctx);
419        let sized = try_cast_ty!(ty, ctx, dyn SizedType);
420        sized.size_bits(ctx)
421    }
422
423    fn unpacked_size_bits(&self, ctx: &Context) -> usize {
424        self.element_ty(ctx).scalar_ty(ctx).size_bits(ctx) / self.packing_factor(ctx)
425    }
426
427    fn align(&self, ctx: &Context) -> usize {
428        let ty = self.get_type(ctx).deref(ctx);
429        let aligned = try_cast_ty!(ty, ctx, dyn AlignedType);
430        aligned.align(ctx)
431    }
432
433    fn is_ptr(&self, ctx: &Context) -> bool {
434        let ty = self.get_type(ctx).deref(ctx);
435        ty.is::<PointerType>()
436    }
437
438    fn is_atomic(&self, ctx: &Context) -> bool {
439        let ty = self.get_type(ctx).deref(ctx);
440        ty.is::<AtomicType>()
441    }
442
443    fn is_vector(&self, ctx: &Context) -> bool {
444        let ty = self.get_type(ctx).deref(ctx);
445        ty.is::<VectorType>()
446    }
447
448    fn is_vector_of_size(&self, ctx: &Context, size: usize) -> bool {
449        let ty = self.get_type(ctx).deref(ctx);
450        ty.downcast_ref::<VectorType>()
451            .is_some_and(|it| it.vectorization == size)
452    }
453
454    fn is_immutable(&self, ctx: &Context) -> bool {
455        !self.is_ptr(ctx)
456    }
457
458    fn vector_size(&self, ctx: &Context) -> usize {
459        let ty = self.get_type(ctx).deref(ctx);
460        let maybe_vec = try_cast_ty!(ty, ctx, dyn MaybeVectorizedType);
461        maybe_vec.vector_size(ctx)
462    }
463
464    fn try_get_vector_size(&self, ctx: &Context) -> Option<usize> {
465        let ty = self.get_type(ctx).deref(ctx);
466        let maybe_vec = type_cast::<dyn MaybeVectorizedType>(&*ty)?;
467        maybe_vec.try_vector_size(ctx)
468    }
469
470    fn packing_factor(&self, ctx: &Context) -> usize {
471        let ty = self.get_type(ctx).deref(ctx);
472        let maybe_packed = try_cast_ty!(ty, ctx, dyn MaybePackedType);
473        maybe_packed.packing_factor(ctx)
474    }
475
476    fn scalar_ty(&self, ctx: &Context) -> TypeHandle {
477        let ty = self.element_ty(ctx).deref(ctx);
478        let scalarizable = try_cast_ty!(ty, ctx, dyn ScalarizableType);
479        scalarizable.scalar_type(ctx)
480    }
481
482    fn element_ty(&self, ctx: &Context) -> TypeHandle {
483        let ty = self.get_type(ctx).deref(ctx);
484        let has_element_type = try_cast_ty!(ty, ctx, dyn HasElementType);
485        has_element_type
486            .element_type(ctx)
487            .expect("Expected element type to be some")
488    }
489
490    fn unwrap_ptr(&self, ctx: &Context) -> TypeHandle {
491        if let Some(ptr) = self.get_type(ctx).deref(ctx).downcast_ref::<PointerType>() {
492            ptr.inner
493        } else {
494            self.get_type(ctx)
495        }
496    }
497
498    fn try_get_scalar_ty(&self, ctx: &Context) -> Option<TypeHandle> {
499        let ty = self.get_type(ctx).deref(ctx);
500        let scalarizable = type_cast::<dyn ScalarizableType>(&*ty)?;
501        Some(scalarizable.scalar_type(ctx))
502    }
503
504    fn try_get_scalar_elem_ty(&self, ctx: &Context) -> Option<TypeHandle> {
505        let ty = self.get_type(ctx).deref(ctx);
506        let has_elem = type_cast::<dyn HasElementType>(&*ty)?;
507        let ty = has_elem.element_type(ctx)?.deref(ctx);
508        let scalarizable = type_cast::<dyn ScalarizableType>(&*ty)?;
509        Some(scalarizable.scalar_type(ctx))
510    }
511
512    fn is_index(&self, ctx: &Context) -> bool {
513        let ty = self.get_type(ctx).deref(ctx);
514        ty.is::<IndexType>()
515    }
516
517    fn is_int(&self, ctx: &Context) -> bool {
518        let ty = self.get_type(ctx).deref(ctx);
519        ty.is::<IntegerType>()
520    }
521
522    fn is_signed_int(&self, ctx: &Context) -> bool {
523        let ty = self.get_type(ctx).deref(ctx);
524        ty.downcast_ref::<IntegerType>()
525            .is_some_and(|it| it.is_signed())
526    }
527
528    fn is_unsigned_int(&self, ctx: &Context) -> bool {
529        let ty = self.get_type(ctx).deref(ctx);
530        ty.downcast_ref::<IntegerType>()
531            .is_some_and(|it| !it.is_signed())
532    }
533
534    fn is_int_of_width(&self, ctx: &Context, width: usize) -> bool {
535        let ty = self.get_type(ctx).deref(ctx);
536        ty.downcast_ref::<IntegerType>()
537            .is_some_and(|it| it.width() as usize == width)
538    }
539
540    fn is_float64(&self, ctx: &Context) -> bool {
541        self.get_type(ctx).deref(ctx).is::<Float64Type>()
542    }
543
544    fn is_float32(&self, ctx: &Context) -> bool {
545        self.get_type(ctx).deref(ctx).is::<Float32Type>()
546    }
547
548    fn is_tfloat32(&self, ctx: &Context) -> bool {
549        self.get_type(ctx).deref(ctx).is::<TFloat32Type>()
550    }
551
552    fn is_float16(&self, ctx: &Context) -> bool {
553        self.get_type(ctx).deref(ctx).is::<Float16Type>()
554    }
555
556    fn is_bfloat16(&self, ctx: &Context) -> bool {
557        self.get_type(ctx).deref(ctx).is::<BFloat16Type>()
558    }
559
560    fn is_float(&self, ctx: &Context) -> bool {
561        self.is_float16(ctx)
562            | self.is_float32(ctx)
563            | self.is_float64(ctx)
564            | self.is_tfloat32(ctx)
565            | self.is_bfloat16(ctx)
566    }
567
568    fn is_bool(&self, ctx: &Context) -> bool {
569        self.get_type(ctx).deref(ctx).is::<BoolType>()
570    }
571}
572
573impl<T: Typed> TypedExt for T {}
574
575pub trait TypeExt {
576    fn as_ptr(&self, ctx: &Context) -> PointerType;
577}
578
579impl TypeExt for TypeHandle {
580    fn as_ptr(&self, ctx: &Context) -> PointerType {
581        *TypedHandle::from_handle(*self, ctx)
582            .expect("Should be pointer")
583            .deref(ctx)
584    }
585}
586
587pub trait ValueExt {
588    fn replace_all_uses_except_with(&self, ctx: &Context, except: Use<Value>, other: &Value);
589}
590
591impl ValueExt for Value {
592    fn replace_all_uses_except_with(&self, ctx: &Context, except: Use<Value>, other: &Value) {
593        self.replace_some_uses_with(ctx, |_, r#use| r#use != &except, other);
594    }
595}