Skip to main content

cubecl_core/frontend/
ranges.rs

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