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