Skip to main content

cubecl_core/frontend/
branch.rs

1use alloc::{boxed::Box, vec::Vec};
2
3use crate::{
4    frontend::RuntimeAssign,
5    ir::Switch,
6    prelude::{CubeEnum, ExpandTypeClone},
7};
8use crate::{
9    ir::{Branch, If, IfElse, Loop, Scope},
10    prelude::Assign,
11};
12
13use super::{Int, NativeExpand};
14
15/// Something that can be iterated on by a for loop. Currently only includes `Range`, `StepBy` and
16/// `Sequence`.
17pub trait Iterable: Sized {
18    type Item;
19
20    /// Expand a runtime loop without unrolling
21    ///
22    /// # Arguments
23    /// # Arguments
24    /// * `scope` - the expansion scope
25    /// * `body` - the loop body to be executed repeatedly
26    fn expand(self, scope: &Scope, body: impl FnMut(&Scope, Self::Item));
27    /// Expand an unrolled loop. The body should be invoced `n` times, where `n` is the number of
28    /// iterations.
29    ///
30    /// # Arguments
31    /// * `scope` - the expansion scope
32    /// * `body` - the loop body to be executed repeatedly
33    fn expand_unroll(self, scope: &Scope, body: impl FnMut(&Scope, Self::Item));
34    /// Return the comptime length of this iterable, if possible
35    fn const_len(&self) -> Option<usize> {
36        None
37    }
38}
39
40pub fn for_expand<I: Iterable>(
41    scope: &Scope,
42    range: I,
43    unroll: bool,
44    body: impl FnMut(&Scope, I::Item),
45) {
46    if unroll || range.const_len() == Some(1) {
47        range.expand_unroll(scope, body);
48    } else {
49        range.expand(scope, body);
50    }
51}
52
53pub fn if_expand(scope: &Scope, condition: NativeExpand<bool>, block: impl FnOnce(&Scope)) {
54    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
55    match comptime_cond {
56        Some(cond) => {
57            if cond {
58                block(scope);
59            }
60        }
61        None => {
62            let child = scope.child();
63
64            block(&child);
65
66            scope.register(Branch::If(Box::new(If {
67                cond: condition.expand,
68                scope: child,
69            })));
70        }
71    }
72}
73
74#[allow(clippy::large_enum_variant)]
75pub enum IfElseExpand {
76    ComptimeThen,
77    ComptimeElse,
78    Runtime {
79        runtime_cond: NativeExpand<bool>,
80        then_child: Scope,
81    },
82}
83
84impl IfElseExpand {
85    pub fn or_else(self, scope: &Scope, else_block: impl FnOnce(&Scope)) {
86        match self {
87            Self::Runtime {
88                runtime_cond,
89                then_child,
90            } => {
91                let else_child = scope.child();
92                else_block(&else_child);
93
94                scope.register(Branch::IfElse(Box::new(IfElse {
95                    cond: runtime_cond.expand,
96                    scope_if: then_child,
97                    scope_else: else_child,
98                })));
99            }
100            Self::ComptimeElse => else_block(scope),
101            Self::ComptimeThen => (),
102        }
103    }
104}
105
106pub fn if_else_expand(
107    scope: &Scope,
108    condition: NativeExpand<bool>,
109    then_block: impl FnOnce(&Scope),
110) -> IfElseExpand {
111    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
112    match comptime_cond {
113        Some(true) => {
114            then_block(scope);
115            IfElseExpand::ComptimeThen
116        }
117        Some(false) => IfElseExpand::ComptimeElse,
118        None => {
119            let then_child = scope.child();
120            then_block(&then_child);
121
122            IfElseExpand::Runtime {
123                runtime_cond: condition,
124                then_child,
125            }
126        }
127    }
128}
129
130#[allow(clippy::large_enum_variant)]
131pub enum IfElseExprExpand<C: Assign> {
132    ComptimeThen(C),
133    ComptimeElse,
134    Runtime {
135        runtime_cond: NativeExpand<bool>,
136        out: C,
137        then_child: Scope,
138    },
139}
140
141impl<C: Assign> IfElseExprExpand<C> {
142    pub fn or_else<R: RuntimeAssign<Expand = C>>(
143        self,
144        scope: &Scope,
145        else_block: impl FnOnce(&Scope) -> R,
146    ) -> C {
147        match self {
148            Self::Runtime {
149                runtime_cond,
150                mut out,
151                then_child,
152            } => {
153                let else_child = scope.child();
154                let ret = else_block(&else_child);
155                out.__expand_assign_method(&else_child, ret.into_expand(scope));
156
157                scope.register(Branch::IfElse(Box::new(IfElse {
158                    cond: runtime_cond.expand,
159                    scope_if: then_child,
160                    scope_else: else_child,
161                })));
162                out
163            }
164            Self::ComptimeElse => else_block(scope).into_expand(scope),
165            Self::ComptimeThen(ret) => ret,
166        }
167    }
168}
169
170pub fn if_else_expr_expand<C: RuntimeAssign>(
171    scope: &Scope,
172    condition: NativeExpand<bool>,
173    then_block: impl FnOnce(&Scope) -> C,
174) -> IfElseExprExpand<C::Expand> {
175    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
176    match comptime_cond {
177        Some(true) => {
178            let ret = then_block(scope);
179            IfElseExprExpand::ComptimeThen(ret.into_expand(scope))
180        }
181        Some(false) => IfElseExprExpand::ComptimeElse,
182        None => {
183            let then_child = scope.child();
184            let ret = then_block(&then_child);
185            let mut out = ret.init_mut(scope);
186            out.__expand_assign_method(&then_child, ret.into_expand(scope));
187
188            IfElseExprExpand::Runtime {
189                runtime_cond: condition,
190                out,
191                then_child,
192            }
193        }
194    }
195}
196
197pub struct SwitchExpand<I: Int> {
198    value: NativeExpand<I>,
199    default: Scope,
200    cases: Vec<(NativeExpand<I>, Scope)>,
201}
202
203impl<I: Int> SwitchExpand<I> {
204    pub fn case(mut self, scope: &Scope, value: impl Int, block: impl FnOnce(&Scope)) -> Self {
205        let value = I::from(value).unwrap();
206        let case_child = scope.child();
207        block(&case_child);
208        self.cases.push((value.into(), case_child));
209        self
210    }
211
212    pub fn finish(self, scope: &Scope) {
213        let value_var = self.value.expand;
214        scope.register(Branch::Switch(Box::new(Switch {
215            value: value_var,
216            scope_default: self.default,
217            cases: self
218                .cases
219                .into_iter()
220                .map(|it| (it.0.expand, it.1))
221                .collect(),
222        })));
223    }
224}
225
226pub fn switch_expand<I: Int>(
227    scope: &Scope,
228    value: NativeExpand<I>,
229    default_block: impl FnOnce(&Scope),
230) -> SwitchExpand<I> {
231    let default_child = scope.child();
232    default_block(&default_child);
233
234    SwitchExpand {
235        value,
236        default: default_child,
237        cases: Vec::new(),
238    }
239}
240
241pub struct SwitchExpandExpr<I: Int, C: Assign> {
242    value: NativeExpand<I>,
243    out: C,
244    default: Scope,
245    cases: Vec<(NativeExpand<I>, Scope)>,
246}
247
248impl<I: Int, C: Assign> SwitchExpandExpr<I, C> {
249    pub fn case<T: RuntimeAssign<Expand = C>>(
250        mut self,
251        scope: &Scope,
252        value: impl Int,
253        block: impl FnOnce(&Scope) -> T,
254    ) -> Self {
255        let value = I::from(value).unwrap();
256        let case_child = scope.child();
257        let ret = block(&case_child);
258        self.out
259            .__expand_assign_method(&case_child, ret.into_expand(scope));
260        self.cases.push((value.into(), case_child));
261        self
262    }
263
264    pub fn finish(self, scope: &Scope) -> C {
265        let value_var = self.value.expand;
266        scope.register(Branch::Switch(Box::new(Switch {
267            value: value_var,
268            scope_default: self.default,
269            cases: self
270                .cases
271                .into_iter()
272                .map(|it| (it.0.expand, it.1))
273                .collect(),
274        })));
275        self.out
276    }
277}
278
279pub fn switch_expand_expr<I: Int, C: RuntimeAssign>(
280    scope: &Scope,
281    value: NativeExpand<I>,
282    default_block: impl FnOnce(&Scope) -> C,
283) -> SwitchExpandExpr<I, C::Expand> {
284    let default_child = scope.child();
285    let default = default_block(&default_child);
286    let mut out = default.init_mut(scope);
287    out.__expand_assign_method(&default_child, default.into_expand(scope));
288
289    SwitchExpandExpr {
290        value,
291        out,
292        default: default_child,
293        cases: Vec::new(),
294    }
295}
296
297#[allow(clippy::large_enum_variant)]
298pub enum MatchExpand<T: CubeEnum> {
299    ComptimeVariant {
300        variant: i32,
301        runtime_value: T::RuntimeValue,
302        matched: bool,
303    },
304    RuntimeVariant {
305        variant: NativeExpand<i32>,
306        cases: Vec<(NativeExpand<i32>, Scope)>,
307        runtime_value: T::RuntimeValue,
308        default: Option<Scope>,
309    },
310}
311
312impl<T: CubeEnum> MatchExpand<T> {
313    pub fn case(
314        mut self,
315        scope: &Scope,
316        value: i32,
317        block: impl FnOnce(&Scope, T::RuntimeValue),
318    ) -> Self {
319        match &mut self {
320            Self::RuntimeVariant {
321                cases,
322                runtime_value,
323                ..
324            } => {
325                let case_child = scope.child();
326                block(&case_child, (*runtime_value).clone_unchecked());
327                cases.push((value.into(), case_child));
328            }
329            Self::ComptimeVariant {
330                variant,
331                runtime_value,
332                matched,
333            } => {
334                if value == *variant {
335                    block(scope, (*runtime_value).clone_unchecked());
336                    *matched = true;
337                }
338            }
339        }
340        self
341    }
342
343    pub fn default(mut self, scope: &Scope, block: impl FnOnce(&Scope, T::RuntimeValue)) -> Self {
344        match &mut self {
345            Self::RuntimeVariant {
346                runtime_value,
347                default,
348                ..
349            } => {
350                let case_child = scope.child();
351                block(&case_child, (*runtime_value).clone_unchecked());
352                *default = Some(case_child);
353            }
354            Self::ComptimeVariant {
355                runtime_value,
356                matched,
357                ..
358            } => {
359                if !*matched {
360                    block(scope, (*runtime_value).clone_unchecked());
361                    *matched = true;
362                }
363            }
364        }
365        self
366    }
367
368    pub fn finish(self, scope: &Scope) {
369        match self {
370            MatchExpand::ComptimeVariant { .. } => {}
371            MatchExpand::RuntimeVariant {
372                variant,
373                cases,
374                default,
375                ..
376            } => {
377                let variant_var = variant.expand;
378                let scope_default = default.unwrap_or_else(|| {
379                    let scope_default = scope.child();
380                    unreachable_unchecked::expand(&scope_default);
381                    scope_default
382                });
383
384                scope.register(Branch::Switch(Box::new(Switch {
385                    value: variant_var,
386                    scope_default,
387                    cases: cases.into_iter().map(|it| (it.0.expand, it.1)).collect(),
388                })));
389            }
390        }
391    }
392}
393
394pub fn match_expand<T: CubeEnum>(
395    scope: &Scope,
396    value: T,
397    discriminant0: i32,
398    arm0: impl FnOnce(&Scope, T::RuntimeValue),
399) -> MatchExpand<T> {
400    let discriminant = value.discriminant();
401    match discriminant.constant() {
402        Some(const_variant) if const_variant.as_i32() == discriminant0 => {
403            let runtime_value = value.runtime_value();
404            arm0(scope, runtime_value.clone_unchecked());
405            MatchExpand::ComptimeVariant {
406                variant: const_variant.as_i32(),
407                runtime_value,
408                matched: true,
409            }
410        }
411        Some(const_variant) => MatchExpand::ComptimeVariant {
412            variant: const_variant.as_i32(),
413            runtime_value: value.runtime_value(),
414            matched: false,
415        },
416        None => {
417            let runtime_value = value.runtime_value();
418            let case_child = scope.child();
419            arm0(&case_child, runtime_value.clone_unchecked());
420
421            MatchExpand::RuntimeVariant {
422                variant: discriminant,
423                cases: alloc::vec![(discriminant0.into(), case_child)],
424                runtime_value,
425                default: None,
426            }
427        }
428    }
429}
430
431#[allow(clippy::large_enum_variant)]
432pub enum MatchExpandExpr<T: CubeEnum, C: Assign> {
433    ComptimeVariant {
434        variant: i32,
435        runtime_value: T::RuntimeValue,
436        out: Option<C>,
437        matched: bool,
438    },
439    RuntimeVariant {
440        variant: NativeExpand<i32>,
441        out: C,
442        cases: Vec<(NativeExpand<i32>, Scope)>,
443        runtime_value: T::RuntimeValue,
444        default: Option<Scope>,
445    },
446}
447
448impl<T: CubeEnum, C: Assign> MatchExpandExpr<T, C> {
449    pub fn case<R: RuntimeAssign<Expand = C>>(
450        mut self,
451        scope: &Scope,
452        value: i32,
453        block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
454    ) -> Self {
455        match &mut self {
456            Self::RuntimeVariant {
457                cases,
458                out,
459                runtime_value,
460                ..
461            } => {
462                let case_child = scope.child();
463                let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
464                out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
465                cases.push((value.into(), case_child));
466            }
467            Self::ComptimeVariant {
468                variant,
469                runtime_value,
470                out,
471                matched,
472            } => {
473                if value == *variant {
474                    *out =
475                        Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
476                    *matched = true;
477                }
478            }
479        }
480        self
481    }
482
483    pub fn default<R: RuntimeAssign<Expand = C>>(
484        mut self,
485        scope: &Scope,
486        block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
487    ) -> Self {
488        match &mut self {
489            Self::RuntimeVariant {
490                runtime_value,
491                out,
492                default,
493                ..
494            } => {
495                let case_child = scope.child();
496                let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
497                out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
498                *default = Some(case_child);
499            }
500            Self::ComptimeVariant {
501                runtime_value,
502                out,
503                matched,
504                ..
505            } => {
506                if !*matched {
507                    *out =
508                        Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
509                    *matched = true;
510                }
511            }
512        }
513        self
514    }
515
516    pub fn finish(self, scope: &Scope) -> C {
517        match self {
518            MatchExpandExpr::ComptimeVariant { out, .. } => {
519                out.expect("At least one variant should be matched")
520            }
521            MatchExpandExpr::RuntimeVariant {
522                variant,
523                cases,
524                out,
525                default,
526                ..
527            } => {
528                let variant_var = variant.expand;
529                let scope_default = default.unwrap_or_else(|| {
530                    let scope_default = scope.child();
531                    unreachable_unchecked::expand(&scope_default);
532                    scope_default
533                });
534                scope.register(Branch::Switch(Box::new(Switch {
535                    value: variant_var,
536                    scope_default,
537                    cases: cases.into_iter().map(|it| (it.0.expand, it.1)).collect(),
538                })));
539                out
540            }
541        }
542    }
543}
544
545pub fn match_expand_expr<T: CubeEnum, C: RuntimeAssign>(
546    scope: &Scope,
547    value: T,
548    discriminant0: i32,
549    arm0: impl FnOnce(&Scope, T::RuntimeValue) -> C,
550) -> MatchExpandExpr<T, C::Expand> {
551    let discriminant = value.discriminant();
552    match discriminant.constant() {
553        Some(const_variant) if const_variant.as_i32() == discriminant0 => {
554            let runtime_value = value.runtime_value();
555            let out = arm0(scope, runtime_value.clone_unchecked());
556            MatchExpandExpr::ComptimeVariant {
557                variant: const_variant.as_i32(),
558                out: Some(out.into_expand(scope)),
559                runtime_value,
560                matched: true,
561            }
562        }
563        Some(const_variant) => MatchExpandExpr::ComptimeVariant {
564            variant: const_variant.as_i32(),
565            out: None,
566            runtime_value: value.runtime_value(),
567            matched: false,
568        },
569        None => {
570            let runtime_value = value.runtime_value();
571            let case_child = scope.child();
572            let ret_val = arm0(&case_child, runtime_value.clone_unchecked());
573
574            let mut out = ret_val.init_mut(scope);
575            out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
576
577            MatchExpandExpr::RuntimeVariant {
578                variant: discriminant,
579                out,
580                cases: alloc::vec![(discriminant0.into(), case_child)],
581                runtime_value,
582                default: None,
583            }
584        }
585    }
586}
587
588pub fn break_expand(scope: &Scope) {
589    scope.register(Branch::Break);
590}
591
592pub fn return_expand(scope: &Scope) {
593    scope.register(Branch::Return);
594}
595
596pub mod unreachable_unchecked {
597    use super::*;
598
599    pub fn expand(scope: &Scope) {
600        scope.register(Branch::Unreachable);
601    }
602}
603
604// Don't make this `FnOnce`, it must be executable multiple times
605pub fn loop_expand(scope: &Scope, mut block: impl FnMut(&Scope)) {
606    let inside_loop = scope.child();
607
608    block(&inside_loop);
609    scope.register(Branch::Loop(Box::new(Loop { scope: inside_loop })));
610}