Skip to main content

cubecl_core/frontend/
ranges.rs

1use alloc::boxed::Box;
2use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
3
4use cubecl_ir::{Branch, RangeLoop, Value};
5use num_traits::NumCast;
6
7use crate as cubecl;
8use cubecl::prelude::*;
9
10#[derive_expand(CubeType)]
11pub struct Range<Idx: Scalar> {
12    start: Idx,
13    end: Idx,
14}
15
16impl<Idx: Scalar> RangeExpand<Idx> {
17    pub fn new(start: NativeExpand<Idx>, end: NativeExpand<Idx>) -> Self {
18        Self { start, end }
19    }
20}
21
22impl<I: Int> RangeExpand<I> {
23    pub fn __expand_step_by_method(
24        self,
25        _: &Scope,
26        n: impl Into<NativeExpand<I>>,
27    ) -> SteppedRangeExpand<I> {
28        SteppedRangeExpand {
29            start: self.start,
30            end: self.end,
31            step: n.into(),
32            inclusive: false,
33        }
34    }
35}
36
37impl<I: Int> Iterable for RangeExpand<I> {
38    type Item = NativeExpand<I>;
39
40    fn expand_unroll(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
41        iter_expand_unroll(scope, self.start, self.end, false, body);
42    }
43
44    fn expand(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
45        iter_expand(scope, self.start, self.end, false, body);
46    }
47
48    fn const_len(&self) -> Option<usize> {
49        let start = self.start.expand.as_const()?.as_i64();
50        let end = self.end.expand.as_const()?.as_i64();
51        Some(start.abs_diff(end) as usize)
52    }
53}
54
55impl IntoSliceIndices for RangeExpand<usize> {
56    fn into_slice_indices<E: CubePrimitive>(
57        self,
58        _scope: &Scope,
59        _list: &impl ListExpand<E>,
60    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
61        (self.start, self.end)
62    }
63}
64
65#[derive_expand(CubeType)]
66pub struct RangeFrom<Idx: Scalar> {
67    start: Idx,
68}
69
70impl<Idx: Scalar> RangeFromExpand<Idx> {
71    pub fn new(start: NativeExpand<Idx>) -> Self {
72        Self { start }
73    }
74}
75
76impl IntoSliceIndices for RangeFromExpand<usize> {
77    fn into_slice_indices<E: CubePrimitive>(
78        self,
79        scope: &Scope,
80        list: &impl ListExpand<E>,
81    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
82        let end = list.__expand_len_method(scope);
83        (self.start, end)
84    }
85}
86
87#[derive_expand(CubeType)]
88pub struct RangeFull;
89
90impl IntoSliceIndices for RangeFullExpand {
91    fn into_slice_indices<E: CubePrimitive>(
92        self,
93        scope: &Scope,
94        list: &impl ListExpand<E>,
95    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
96        let start = NativeExpand::from_lit(scope, 0);
97        let end = list.__expand_len_method(scope);
98        (start, end)
99    }
100}
101
102#[derive_expand(CubeType)]
103pub struct RangeInclusive<Idx: Scalar> {
104    start: Idx,
105    last: Idx,
106}
107
108impl<Idx: Scalar> RangeInclusiveExpand<Idx> {
109    pub fn new(start: NativeExpand<Idx>, last: NativeExpand<Idx>) -> Self {
110        Self { start, last }
111    }
112}
113
114impl<I: Int> RangeInclusiveExpand<I> {
115    pub fn __expand_step_by_method(
116        self,
117        _: &Scope,
118        n: impl Into<NativeExpand<I>>,
119    ) -> SteppedRangeExpand<I> {
120        SteppedRangeExpand {
121            start: self.start,
122            end: self.last,
123            step: n.into(),
124            inclusive: true,
125        }
126    }
127}
128
129impl<I: Int> Iterable for RangeInclusiveExpand<I> {
130    type Item = NativeExpand<I>;
131
132    fn expand_unroll(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
133        iter_expand_unroll(scope, self.start, self.last, true, body);
134    }
135
136    fn expand(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
137        iter_expand(scope, self.start, self.last, true, body);
138    }
139
140    fn const_len(&self) -> Option<usize> {
141        let start = self.start.expand.as_const()?.as_i64();
142        let end = self.last.expand.as_const()?.as_i64();
143        Some(start.abs_diff(end) as usize + 1)
144    }
145}
146
147impl IntoSliceIndices for RangeInclusiveExpand<usize> {
148    fn into_slice_indices<E: CubePrimitive>(
149        self,
150        scope: &Scope,
151        _list: &impl ListExpand<E>,
152    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
153        let end = self
154            .last
155            .__expand_add_method(scope, NativeExpand::from_lit(scope, 1));
156        (self.start, end)
157    }
158}
159
160#[derive_expand(CubeType)]
161pub struct RangeTo<Idx: Scalar> {
162    end: Idx,
163}
164
165impl<Idx: Scalar> RangeToExpand<Idx> {
166    pub fn new(end: NativeExpand<Idx>) -> Self {
167        Self { end }
168    }
169}
170
171impl<I: Int> RangeToExpand<I> {
172    pub fn __expand_step_by_method(
173        self,
174        scope: &Scope,
175        n: impl Into<NativeExpand<I>>,
176    ) -> SteppedRangeExpand<I> {
177        SteppedRangeExpand {
178            start: NativeExpand::from_lit(scope, I::new(0)),
179            end: self.end,
180            step: n.into(),
181            inclusive: false,
182        }
183    }
184}
185
186impl<I: Int> Iterable for RangeToExpand<I> {
187    type Item = NativeExpand<I>;
188
189    fn expand_unroll(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
190        let start = NativeExpand::from_lit(scope, I::new(0));
191        iter_expand_unroll(scope, start, self.end, false, body);
192    }
193
194    fn expand(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
195        let start = NativeExpand::from_lit(scope, I::new(0));
196        iter_expand(scope, start, self.end, false, body);
197    }
198
199    fn const_len(&self) -> Option<usize> {
200        Some(self.end.expand.as_const()?.as_usize())
201    }
202}
203
204impl IntoSliceIndices for RangeToExpand<usize> {
205    fn into_slice_indices<E: CubePrimitive>(
206        self,
207        scope: &Scope,
208        _list: &impl ListExpand<E>,
209    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
210        let start = NativeExpand::from_lit(scope, 0);
211        (start, self.end)
212    }
213}
214
215#[derive_expand(CubeType)]
216pub struct RangeToInclusive<Idx: Scalar> {
217    last: Idx,
218}
219
220impl<Idx: Scalar> RangeToInclusiveExpand<Idx> {
221    pub fn new(last: NativeExpand<Idx>) -> Self {
222        Self { last }
223    }
224}
225
226impl<I: Int> RangeToInclusiveExpand<I> {
227    pub fn __expand_step_by_method(
228        self,
229        scope: &Scope,
230        n: impl Into<NativeExpand<I>>,
231    ) -> SteppedRangeExpand<I> {
232        SteppedRangeExpand {
233            start: NativeExpand::from_lit(scope, I::new(0)),
234            end: self.last,
235            step: n.into(),
236            inclusive: true,
237        }
238    }
239}
240
241impl<I: Int> Iterable for RangeToInclusiveExpand<I> {
242    type Item = NativeExpand<I>;
243
244    fn expand_unroll(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
245        let start = NativeExpand::from_lit(scope, I::new(0));
246        iter_expand_unroll(scope, start, self.last, true, body);
247    }
248
249    fn expand(self, scope: &Scope, body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
250        let start = NativeExpand::from_lit(scope, I::new(0));
251        iter_expand(scope, start, self.last, true, body);
252    }
253
254    fn const_len(&self) -> Option<usize> {
255        Some(self.last.expand.as_const()?.as_usize() + 1)
256    }
257}
258
259impl IntoSliceIndices for RangeToInclusiveExpand<usize> {
260    fn into_slice_indices<E: CubePrimitive>(
261        self,
262        scope: &Scope,
263        _list: &impl ListExpand<E>,
264    ) -> (NativeExpand<usize>, NativeExpand<usize>) {
265        let start = NativeExpand::from_lit(scope, 0);
266        let end = self
267            .last
268            .__expand_add_method(scope, NativeExpand::from_lit(scope, 1));
269        (start, end)
270    }
271}
272
273pub(crate) trait IntoSliceIndices {
274    fn into_slice_indices<E: CubePrimitive>(
275        self,
276        scope: &Scope,
277        list: &impl ListExpand<E>,
278    ) -> (NativeExpand<usize>, NativeExpand<usize>);
279}
280
281macro_rules! impl_slice_ranges {
282    ($ty: ty, $range: ty) => {
283        impl<E: CubePrimitive> IndexExpand<$range> for $ty {
284            type Output = SliceExpand<E>;
285
286            fn __expand_index_method(&self, scope: &Scope, index: $range) -> &Self::Output {
287                let (start, end) = index.into_slice_indices(scope, self);
288                self.__expand_slice_method(scope, start, end)
289            }
290        }
291
292        impl<E: CubePrimitive> IndexMutExpand<$range> for $ty {
293            fn __expand_index_mut_method(
294                &mut self,
295                scope: &Scope,
296                index: $range,
297            ) -> &mut <Self as IndexExpand<$range>>::Output {
298                let (start, end) = index.into_slice_indices(scope, self);
299                self.__expand_slice_mut_method(scope, start, end)
300            }
301        }
302    };
303    ($ty: ty) => {
304        impl_slice_ranges!($ty, RangeExpand<usize>);
305        impl_slice_ranges!($ty, RangeFromExpand<usize>);
306        impl_slice_ranges!($ty, RangeFullExpand);
307        impl_slice_ranges!($ty, RangeInclusiveExpand<usize>);
308        impl_slice_ranges!($ty, RangeToExpand<usize>);
309        impl_slice_ranges!($ty, RangeToInclusiveExpand<usize>);
310    };
311}
312pub(crate) use impl_slice_ranges;
313
314fn iter_expand_unroll<I: Int>(
315    scope: &Scope,
316    start: NativeExpand<I>,
317    end: NativeExpand<I>,
318    inclusive: bool,
319    mut body: impl FnMut(&Scope, <I as CubeType>::ExpandType),
320) {
321    let start = start
322        .expand
323        .as_const()
324        .expect("Only constant start can be unrolled.")
325        .as_i64();
326    let end = end
327        .expand
328        .as_const()
329        .expect("Only constant end can be unrolled.")
330        .as_i64();
331
332    if inclusive {
333        for i in start..=end {
334            let val = I::from_int(i);
335            body(scope, val.into())
336        }
337    } else {
338        for i in start..end {
339            let val = I::from_int(i);
340            body(scope, val.into())
341        }
342    }
343}
344
345fn iter_expand<I: Int>(
346    scope: &Scope,
347    start: NativeExpand<I>,
348    end: NativeExpand<I>,
349    inclusive: bool,
350    mut body: impl FnMut(&Scope, <I as CubeType>::ExpandType),
351) {
352    let mut child = scope.child();
353    let index_ty = I::__expand_as_type(scope);
354    let i = scope.create_local_mut(index_ty);
355
356    body(&mut child, i.into());
357
358    let start = I::__expand_cast_from(scope, start).expand;
359    let end = I::__expand_cast_from(scope, end).expand;
360
361    scope.register(Branch::RangeLoop(Box::new(RangeLoop {
362        i,
363        start,
364        end,
365        step: None,
366        scope: child,
367        inclusive,
368    })));
369}
370
371pub struct SteppedRangeExpand<I: Int> {
372    start: NativeExpand<I>,
373    end: NativeExpand<I>,
374    step: NativeExpand<I>,
375    inclusive: bool,
376}
377
378impl<I: Int + Into<Value>> Iterable for SteppedRangeExpand<I> {
379    type Item = NativeExpand<I>;
380
381    fn expand(self, scope: &Scope, mut body: impl FnMut(&Scope, <I as CubeType>::ExpandType)) {
382        let child = scope.child();
383        let index_ty = I::__expand_as_type(scope);
384        let i = scope.create_local_mut(index_ty);
385
386        body(&child, i.into());
387
388        scope.register(Branch::RangeLoop(Box::new(RangeLoop {
389            i,
390            start: self.start.expand,
391            end: self.end.expand,
392            step: Some(self.step.expand),
393            scope: child,
394            inclusive: self.inclusive,
395        })));
396    }
397
398    fn expand_unroll(
399        self,
400        scope: &Scope,
401        mut body: impl FnMut(&Scope, <I as CubeType>::ExpandType),
402    ) {
403        let start = self
404            .start
405            .expand
406            .as_const()
407            .expect("Only constant start can be unrolled.")
408            .as_i128();
409        let end = self
410            .end
411            .expand
412            .as_const()
413            .expect("Only constant end can be unrolled.")
414            .as_i128();
415        let step = self
416            .step
417            .expand
418            .as_const()
419            .expect("Only constant step can be unrolled.")
420            .as_i128();
421
422        match (self.inclusive, step.is_negative()) {
423            (true, true) => {
424                for i in (end..=start).rev().step_by(step.unsigned_abs() as usize) {
425                    let val = I::from_int_128(i);
426                    body(scope, val.into())
427                }
428            }
429            (true, false) => {
430                for i in (start..=end).step_by(step.unsigned_abs() as usize) {
431                    let val = I::from_int_128(i);
432                    body(scope, val.into())
433                }
434            }
435            (false, true) => {
436                for i in (end..start).rev().step_by(step.unsigned_abs() as usize) {
437                    let val = I::from_int_128(i);
438                    body(scope, val.into())
439                }
440            }
441            (false, false) => {
442                for i in (start..end).step_by(step.unsigned_abs() as usize) {
443                    let val = I::from_int_128(i);
444                    body(scope, val.into())
445                }
446            }
447        }
448    }
449
450    fn const_len(&self) -> Option<usize> {
451        let start = self.start.constant()?.as_i128();
452        let end = self.end.constant()?.as_i128();
453        let step = self.step.constant()?.as_i128().unsigned_abs();
454        Some((start.abs_diff(end) / step) as usize)
455    }
456}
457
458/// integer range. Equivalent to:
459///
460/// ```ignore
461/// start..end
462/// ```
463pub fn range<T: Int>(start: T, end: T) -> impl Iterator<Item = T> {
464    let start: i64 = start.to_i64().unwrap();
465    let end: i64 = end.to_i64().unwrap();
466    (start..end).map(<T as NumCast>::from).map(Option::unwrap)
467}
468
469pub mod range {
470    use cubecl_ir::Scope;
471
472    use crate::prelude::{Int, NativeExpand};
473
474    use super::RangeExpand;
475
476    pub fn expand<I: Int>(
477        _scope: &Scope,
478        start: NativeExpand<I>,
479        end: NativeExpand<I>,
480    ) -> RangeExpand<I> {
481        RangeExpand { start, end }
482    }
483}
484
485/// Stepped range. Equivalent to:
486///
487/// ```ignore
488/// (start..end).step_by(step)
489/// ```
490///
491/// Allows using any integer for the step, instead of just usize
492pub fn range_stepped<I: Int>(start: I, end: I, step: I) -> Box<dyn Iterator<Item = I>> {
493    let start = start.to_i128().unwrap();
494    let end = end.to_i128().unwrap();
495    let step = step.to_i128().unwrap();
496
497    if step < 0 {
498        Box::new(
499            (end..start)
500                .rev()
501                .step_by(step.unsigned_abs() as usize)
502                .map(<I as NumCast>::from)
503                .map(Option::unwrap),
504        )
505    } else {
506        Box::new(
507            (start..end)
508                .step_by(step.unsigned_abs() as usize)
509                .map(<I as NumCast>::from)
510                .map(Option::unwrap),
511        )
512    }
513}
514
515pub mod range_stepped {
516    use cubecl_ir::Scope;
517
518    use crate::prelude::{Int, NativeExpand};
519
520    use super::SteppedRangeExpand;
521
522    pub fn expand<I: Int>(
523        _scope: &Scope,
524        start: NativeExpand<I>,
525        end: NativeExpand<I>,
526        step: NativeExpand<I>,
527    ) -> SteppedRangeExpand<I> {
528        SteppedRangeExpand {
529            start,
530            end,
531            step,
532            inclusive: false,
533        }
534    }
535}