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    if inclusive {
348        for i in start..=end {
349            let val = I::from_int(i);
350            body(scope, val.into())
351        }
352    } else {
353        for i in start..end {
354            let val = I::from_int(i);
355            body(scope, val.into())
356        }
357    }
358}
359
360fn iter_expand<I: Int>(
361    scope: &Scope,
362    start: NativeExpand<I>,
363    end: NativeExpand<I>,
364    inclusive: bool,
365    body: &mut dyn FnMut(&Scope, <I as CubeType>::ExpandType),
366) {
367    let start = I::__expand_cast_from(scope, start).expand;
368    let mut end = I::__expand_cast_from(scope, end);
369    let step: ExpandValue = I::new(1).into();
370
371    if inclusive {
372        end = end.__expand_add_method(scope, I::new(1).into());
373    }
374
375    let start = start.read_value(scope);
376    let end = end.read_value(scope);
377    let step = step.read_value(scope);
378
379    let range_loop = RangeLoopOp::new(scope.ctx_mut(), start, end, step);
380    let i = range_loop.iter_var(scope.ctx());
381    let body_block = range_loop.loop_body(scope.ctx());
382    let child = scope.loop_child(OpInserter::new_at_block_end(body_block));
383
384    body(&child, i.into());
385    child.terminate_yield();
386
387    register_range_loop::<I>(scope, &range_loop, &child);
388    scope.set_may_return(&[child]);
389}
390
391pub struct SteppedRangeExpand<I: Int> {
392    start: NativeExpand<I>,
393    end: NativeExpand<I>,
394    step: NativeExpand<I>,
395    inclusive: bool,
396}
397
398impl<I: Int + Into<ExpandValue>> Iterable for SteppedRangeExpand<I> {
399    type Item = NativeExpand<I>;
400
401    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, <I as CubeType>::ExpandType)) {
402        let mut end = self.end;
403        if self.inclusive {
404            end = end.__expand_add_method(scope, I::new(1).into());
405        }
406
407        let start = self.start.read_value(scope);
408        let end = end.read_value(scope);
409        let step = self.step.read_value(scope);
410
411        let range_loop = RangeLoopOp::new(scope.ctx_mut(), start, end, step);
412        let i = range_loop.iter_var(scope.ctx());
413        let body_block = range_loop.loop_body(scope.ctx());
414        let child = scope.loop_child(OpInserter::new_at_block_end(body_block));
415
416        body(&child, i.into());
417
418        register_range_loop::<I>(scope, &range_loop, &child);
419        scope.set_may_return(&[child]);
420    }
421
422    fn expand_unroll(
423        self,
424        scope: &Scope,
425        body: &mut dyn FnMut(&Scope, <I as CubeType>::ExpandType),
426    ) {
427        let start = self
428            .start
429            .expand
430            .as_const()
431            .expect("Only constant start can be unrolled.")
432            .as_i128();
433        let end = self
434            .end
435            .expand
436            .as_const()
437            .expect("Only constant end can be unrolled.")
438            .as_i128();
439        let step = self
440            .step
441            .expand
442            .as_const()
443            .expect("Only constant step can be unrolled.")
444            .as_i128();
445
446        match (self.inclusive, step.is_negative()) {
447            (true, true) => {
448                for i in (end..=start).rev().step_by(step.unsigned_abs() as usize) {
449                    let val = I::from_int_128(i);
450                    body(scope, val.into())
451                }
452            }
453            (true, false) => {
454                for i in (start..=end).step_by(step.unsigned_abs() as usize) {
455                    let val = I::from_int_128(i);
456                    body(scope, val.into())
457                }
458            }
459            (false, true) => {
460                for i in (end..start).rev().step_by(step.unsigned_abs() as usize) {
461                    let val = I::from_int_128(i);
462                    body(scope, val.into())
463                }
464            }
465            (false, false) => {
466                for i in (start..end).step_by(step.unsigned_abs() as usize) {
467                    let val = I::from_int_128(i);
468                    body(scope, val.into())
469                }
470            }
471        }
472    }
473
474    fn const_len(&self) -> Option<usize> {
475        let start = self.start.constant()?.as_i128();
476        let end = self.end.constant()?.as_i128();
477        let step = self.step.constant()?.as_i128().unsigned_abs();
478        Some((start.abs_diff(end) / step) as usize)
479    }
480}
481
482/// integer range. Equivalent to:
483///
484/// ```ignore
485/// start..end
486/// ```
487pub fn range<T: Int>(start: T, end: T) -> impl Iterator<Item = T> {
488    let start: i64 = start.to_i64().unwrap();
489    let end: i64 = end.to_i64().unwrap();
490    (start..end).map(<T as NumCast>::from).map(Option::unwrap)
491}
492
493pub mod range {
494    use cubecl_ir::Scope;
495
496    use crate::prelude::{Int, NativeExpand};
497
498    use super::RangeExpand;
499
500    pub fn expand<I: Int>(
501        _scope: &Scope,
502        start: NativeExpand<I>,
503        end: NativeExpand<I>,
504    ) -> RangeExpand<I> {
505        RangeExpand { start, end }
506    }
507}
508
509/// Stepped range. Equivalent to:
510///
511/// ```ignore
512/// (start..end).step_by(step)
513/// ```
514///
515/// Allows using any integer for the step, instead of just usize
516pub fn range_stepped<I: Int>(start: I, end: I, step: I) -> Box<dyn Iterator<Item = I>> {
517    let start = start.to_i128().unwrap();
518    let end = end.to_i128().unwrap();
519    let step = step.to_i128().unwrap();
520
521    if step < 0 {
522        Box::new(
523            (end..start)
524                .rev()
525                .step_by(step.unsigned_abs() as usize)
526                .map(<I as NumCast>::from)
527                .map(Option::unwrap),
528        )
529    } else {
530        Box::new(
531            (start..end)
532                .step_by(step.unsigned_abs() as usize)
533                .map(<I as NumCast>::from)
534                .map(Option::unwrap),
535        )
536    }
537}
538
539pub mod range_stepped {
540    use cubecl_ir::Scope;
541
542    use crate::prelude::{Int, NativeExpand};
543
544    use super::SteppedRangeExpand;
545
546    pub fn expand<I: Int>(
547        _scope: &Scope,
548        start: NativeExpand<I>,
549        end: NativeExpand<I>,
550        step: NativeExpand<I>,
551    ) -> SteppedRangeExpand<I> {
552        SteppedRangeExpand {
553            start,
554            end,
555            step,
556            inclusive: false,
557        }
558    }
559}