Skip to main content

cubecl_core/frontend/
branch.rs

1use alloc::vec::Vec;
2use cubecl_ir::{
3    ExpandState, ExpandValue, OpInserter,
4    dialect::{
5        branch::{ConditionOp, IfOp, RangeLoopOp, SwitchOp, UnreachableOp, WhileOp},
6        general::BoolAndOp,
7    },
8    pliron::{irbuild::inserter::Inserter, r#type::TypedHandle},
9    read_value,
10    rewrite::{op_insertion_point_in_block, set_inserter_before_terminator},
11};
12use pliron::{
13    basic_block::BasicBlock,
14    builtin::{
15        attributes::IntegerAttr,
16        op_interfaces::OneRegionInterface,
17        types::{IntegerType, Signedness},
18    },
19    irbuild::{
20        listener::DummyListener,
21        rewriter::{IRRewriter, Rewriter},
22    },
23    op::Op,
24    region::Region,
25    r#type::Typed,
26    utils::apint::{APInt, bw},
27    value::Value,
28};
29
30use crate::{
31    IntoRuntime,
32    frontend::{
33        CubePrimitive, ReadValue, RuntimeAssign, assign, assign_binop_expand, binary_expand,
34        init_mut_of_type,
35    },
36    prelude::{CubeEnum, ExpandTypeClone},
37};
38use crate::{ir::Scope, prelude::Assign};
39
40use super::{Int, NativeExpand};
41
42/// Something that can be iterated on by a for loop. Currently only includes `Range`, `StepBy` and
43/// `Sequence`.
44pub trait Iterable: Sized {
45    type Item;
46
47    /// Expand a runtime loop without unrolling
48    ///
49    /// # Arguments
50    /// * `scope` - the expansion scope
51    /// * `body` - the loop body to be executed repeatedly
52    ///
53    /// The body is a trait object rather than an `impl FnMut` so that this
54    /// expands once per iterable instead of once per loop body. See
55    /// [`for_expand`].
56    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, Self::Item));
57    /// Expand an unrolled loop. The body should be invoced `n` times, where `n` is the number of
58    /// iterations.
59    ///
60    /// # Arguments
61    /// * `scope` - the expansion scope
62    /// * `body` - the loop body to be executed repeatedly
63    fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, Self::Item));
64    /// Return the comptime length of this iterable, if possible
65    fn const_len(&self) -> Option<usize> {
66        None
67    }
68}
69
70/// Expand a `for` loop over `range`.
71///
72/// Every `for` in every kernel reaches here with a body of its own anonymous
73/// closure type. Staying generic over that type all the way down would expand
74/// the whole loop machinery once per loop written anywhere — thousands of
75/// near-identical copies in a kernel-heavy crate. So this is a shim: it erases
76/// the body to a trait object straight away and hands off to
77/// [`for_expand_dyn`], which expands once per iterable. Expansion runs on the
78/// host while building a kernel, never on the device, so the indirection costs
79/// nothing that reaches the GPU.
80pub fn for_expand<I: Iterable>(
81    scope: &Scope,
82    range: I,
83    unroll: bool,
84    mut body: impl FnMut(&Scope, I::Item),
85) {
86    for_expand_dyn(scope, range, unroll, &mut body);
87}
88
89fn for_expand_dyn<I: Iterable>(
90    scope: &Scope,
91    range: I,
92    unroll: bool,
93    body: &mut dyn FnMut(&Scope, I::Item),
94) {
95    if unroll || range.const_len() == Some(1) {
96        range.expand_unroll(scope, body);
97    } else {
98        range.expand(scope, body);
99    }
100}
101
102pub fn if_expand(scope: &Scope, condition: NativeExpand<bool>, block: impl FnOnce(&Scope)) {
103    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
104    match comptime_cond {
105        Some(cond) => {
106            if cond {
107                block(scope);
108            }
109        }
110        None => {
111            let cond = condition.read_value(scope);
112            let if_op = IfOp::new(scope.ctx_mut(), cond);
113
114            let then_block = if_op.then_block(scope.ctx());
115            let then_child = scope.branch_child(OpInserter::new_at_block_end(then_block));
116            block(&then_child);
117            then_child.terminate_yield();
118
119            let else_block = if_op.else_block(scope.ctx());
120            let else_child = scope.branch_child(OpInserter::new_at_block_end(else_block));
121            else_child.terminate_yield();
122
123            scope.register(&if_op);
124            scope.update_flags_after_branch(&[then_child, else_child]);
125        }
126    }
127}
128
129#[allow(clippy::large_enum_variant)]
130pub enum IfElseExpand {
131    ComptimeThen,
132    ComptimeElse,
133    Runtime {
134        runtime_cond: NativeExpand<bool>,
135        if_op: IfOp,
136        then_child: Scope,
137    },
138}
139
140impl IfElseExpand {
141    pub fn or_else(self, scope: &Scope, else_block: impl FnOnce(&Scope)) {
142        match self {
143            Self::Runtime {
144                if_op, then_child, ..
145            } => {
146                let else_body = if_op.else_block(scope.ctx());
147                let else_child = scope.branch_child(OpInserter::new_at_block_end(else_body));
148                else_block(&else_child);
149                else_child.terminate_yield();
150
151                scope.register(&if_op);
152                scope.update_flags_after_branch(&[then_child, else_child]);
153            }
154            Self::ComptimeElse => else_block(scope),
155            Self::ComptimeThen => (),
156        }
157    }
158}
159
160pub fn if_else_expand(
161    scope: &Scope,
162    condition: NativeExpand<bool>,
163    then_block: impl FnOnce(&Scope),
164) -> IfElseExpand {
165    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
166    match comptime_cond {
167        Some(true) => {
168            then_block(scope);
169            IfElseExpand::ComptimeThen
170        }
171        Some(false) => IfElseExpand::ComptimeElse,
172        None => {
173            let cond = condition.read_value(scope);
174            let if_op = IfOp::new(scope.ctx_mut(), cond);
175            let if_block = if_op.then_block(scope.ctx());
176            let then_child = scope.branch_child(OpInserter::new_at_block_end(if_block));
177            then_block(&then_child);
178            then_child.terminate_yield();
179
180            IfElseExpand::Runtime {
181                runtime_cond: condition,
182                if_op,
183                then_child,
184            }
185        }
186    }
187}
188
189#[allow(clippy::large_enum_variant)]
190pub enum IfElseExprExpand<C: Assign> {
191    ComptimeThen(C),
192    ComptimeElse,
193    Runtime {
194        runtime_cond: NativeExpand<bool>,
195        out: C,
196        if_op: IfOp,
197        then_child: Scope,
198    },
199}
200
201impl<C: Assign> IfElseExprExpand<C> {
202    pub fn or_else<R: RuntimeAssign<Expand = C>>(
203        self,
204        scope: &Scope,
205        else_block: impl FnOnce(&Scope) -> R,
206    ) -> C {
207        match self {
208            Self::Runtime {
209                mut out,
210                if_op,
211                then_child,
212                ..
213            } => {
214                let else_body = if_op.else_block(scope.ctx());
215                let else_child = scope.branch_child(OpInserter::new_at_block_end(else_body));
216                let ret = else_block(&else_child);
217                out.__expand_assign_method(&else_child, ret.into_expand(scope));
218                else_child.terminate_yield();
219
220                scope.register(&if_op);
221                scope.update_flags_after_branch(&[then_child, else_child]);
222                out
223            }
224            Self::ComptimeElse => else_block(scope).into_expand(scope),
225            Self::ComptimeThen(ret) => ret,
226        }
227    }
228}
229
230pub fn if_else_expr_expand<C: RuntimeAssign>(
231    scope: &Scope,
232    condition: NativeExpand<bool>,
233    then_block: impl FnOnce(&Scope) -> C,
234) -> IfElseExprExpand<C::Expand> {
235    let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
236    match comptime_cond {
237        Some(true) => {
238            let ret = then_block(scope);
239            IfElseExprExpand::ComptimeThen(ret.into_expand(scope))
240        }
241        Some(false) => IfElseExprExpand::ComptimeElse,
242        None => {
243            let cond = condition.read_value(scope);
244            let if_op = IfOp::new(scope.ctx_mut(), cond);
245            let then_body = if_op.then_block(scope.ctx());
246            let then_child = scope.branch_child(OpInserter::new_at_block_end(then_body));
247            let ret = then_block(&then_child);
248            let mut out = ret.init_mut(scope);
249            out.__expand_assign_method(&then_child, ret.into_expand(scope));
250            then_child.terminate_yield();
251
252            IfElseExprExpand::Runtime {
253                runtime_cond: condition,
254                out,
255                if_op,
256                then_child,
257            }
258        }
259    }
260}
261
262pub struct SwitchExpand<I: Int> {
263    switch_op: SwitchOp,
264    cases: Vec<I>,
265    children: Vec<Scope>,
266}
267
268impl<I: Int> SwitchExpand<I> {
269    pub fn case(mut self, scope: &Scope, value: impl Int, block: impl FnOnce(&Scope)) -> Self {
270        let value = I::from(value).unwrap();
271        self.cases.push(value);
272        let body = self.switch_op.append_case_block(scope.ctx_mut());
273        let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
274        block(&case_child);
275        case_child.terminate_yield();
276        self.children.push(case_child);
277        self
278    }
279
280    pub fn finish(self, scope: &Scope) {
281        let cases = self.cases.into_iter().map(|case| {
282            let ty = I::__expand_as_type(scope);
283            let ty = TypedHandle::<IntegerType>::from_handle(ty, scope.ctx()).unwrap();
284            let width = bw(ty.deref(scope.ctx()).width() as usize);
285            IntegerAttr::new(ty, APInt::from_i128(case.to_i128().unwrap(), width))
286        });
287        self.switch_op.set_attr_cases(scope.ctx(), cases);
288        scope.register(&self.switch_op);
289        scope.update_flags_after_branch(&self.children);
290    }
291}
292
293pub fn switch_expand<I: Int>(
294    scope: &Scope,
295    value: NativeExpand<I>,
296    default_block: impl FnOnce(&Scope),
297) -> SwitchExpand<I> {
298    let value = value.read_value(scope);
299    let switch_op = SwitchOp::new(scope.ctx_mut(), value);
300
301    let default_body = switch_op.default_block(scope.ctx());
302    let default_child = scope.branch_child(OpInserter::new_at_block_end(default_body));
303    default_block(&default_child);
304    default_child.terminate_yield();
305
306    SwitchExpand {
307        switch_op,
308        cases: Vec::new(),
309        children: alloc::vec![default_child],
310    }
311}
312
313pub struct SwitchExpandExpr<I: Int, C: Assign> {
314    switch_op: SwitchOp,
315    cases: Vec<I>,
316    children: Vec<Scope>,
317    out: C,
318}
319
320impl<I: Int, C: Assign> SwitchExpandExpr<I, C> {
321    pub fn case<T: RuntimeAssign<Expand = C>>(
322        mut self,
323        scope: &Scope,
324        value: impl Int,
325        block: impl FnOnce(&Scope) -> T,
326    ) -> Self {
327        let value = I::from(value).unwrap();
328        self.cases.push(value);
329        let body = self.switch_op.append_case_block(scope.ctx_mut());
330        let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
331        let ret = block(&case_child);
332        self.out
333            .__expand_assign_method(&case_child, ret.into_expand(scope));
334        case_child.terminate_yield();
335        self.children.push(case_child);
336        self
337    }
338
339    pub fn finish(self, scope: &Scope) -> C {
340        let cases = self.cases.into_iter().map(|case| {
341            let ty = I::__expand_as_type(scope);
342            let ty = TypedHandle::<IntegerType>::from_handle(ty, scope.ctx()).unwrap();
343            let width = bw(ty.deref(scope.ctx()).width() as usize);
344            IntegerAttr::new(ty, APInt::from_i128(case.to_i128().unwrap(), width))
345        });
346        self.switch_op.set_attr_cases(scope.ctx(), cases);
347        scope.register(&self.switch_op);
348        scope.update_flags_after_branch(&self.children);
349        self.out
350    }
351}
352
353pub fn switch_expand_expr<I: Int, C: RuntimeAssign>(
354    scope: &Scope,
355    value: NativeExpand<I>,
356    default_block: impl FnOnce(&Scope) -> C,
357) -> SwitchExpandExpr<I, C::Expand> {
358    let value = value.read_value(scope);
359    let switch_op = SwitchOp::new(scope.ctx_mut(), value);
360
361    let default_body = switch_op.default_block(scope.ctx());
362    let default_child = scope.branch_child(OpInserter::new_at_block_end(default_body));
363    let default = default_block(&default_child);
364    let mut out = default.init_mut(scope);
365    out.__expand_assign_method(&default_child, default.into_expand(scope));
366    default_child.terminate_yield();
367
368    SwitchExpandExpr {
369        switch_op,
370        cases: Vec::new(),
371        children: alloc::vec![default_child],
372        out,
373    }
374}
375
376#[allow(clippy::large_enum_variant)]
377pub enum MatchExpand<T: CubeEnum> {
378    ComptimeVariant {
379        variant: i32,
380        runtime_value: T::RuntimeValue,
381        matched: bool,
382    },
383    RuntimeVariant {
384        switch_op: SwitchOp,
385        cases: Vec<i32>,
386        children: Vec<Scope>,
387        has_default: bool,
388        runtime_value: T::RuntimeValue,
389    },
390}
391
392impl<T: CubeEnum> MatchExpand<T> {
393    pub fn case(
394        mut self,
395        scope: &Scope,
396        value: i32,
397        block: impl FnOnce(&Scope, T::RuntimeValue),
398    ) -> Self {
399        match &mut self {
400            Self::RuntimeVariant {
401                switch_op,
402                cases,
403                children,
404                runtime_value,
405                ..
406            } => {
407                cases.push(value);
408                let body = switch_op.append_case_block(scope.ctx_mut());
409                let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
410                block(&case_child, (*runtime_value).clone_unchecked());
411                case_child.terminate_yield();
412                children.push(case_child);
413            }
414            Self::ComptimeVariant {
415                variant,
416                runtime_value,
417                matched,
418            } => {
419                if value == *variant {
420                    block(scope, (*runtime_value).clone_unchecked());
421                    *matched = true;
422                }
423            }
424        }
425        self
426    }
427
428    pub fn default(mut self, scope: &Scope, block: impl FnOnce(&Scope, T::RuntimeValue)) -> Self {
429        match &mut self {
430            Self::RuntimeVariant {
431                switch_op,
432                children,
433                runtime_value,
434                has_default,
435                ..
436            } => {
437                let body = switch_op.default_block(scope.ctx());
438                let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
439                block(&case_child, (*runtime_value).clone_unchecked());
440                case_child.terminate_yield();
441                children.push(case_child);
442                *has_default = true;
443            }
444            Self::ComptimeVariant {
445                runtime_value,
446                matched,
447                ..
448            } => {
449                if !*matched {
450                    block(scope, (*runtime_value).clone_unchecked());
451                    *matched = true;
452                }
453            }
454        }
455        self
456    }
457
458    pub fn finish(self, scope: &Scope) {
459        match self {
460            MatchExpand::ComptimeVariant { .. } => {}
461            MatchExpand::RuntimeVariant {
462                switch_op,
463                cases,
464                children,
465                has_default,
466                ..
467            } => {
468                if !has_default {
469                    let default_body = switch_op.default_block(scope.ctx());
470                    let mut inserter = OpInserter::new_at_block_end(default_body);
471                    let unreachable = UnreachableOp::new(scope.ctx_mut());
472                    inserter.append_op(scope.ctx(), &unreachable);
473                }
474
475                let cases = cases.into_iter().map(|case| {
476                    let ty = IntegerType::get(scope.ctx(), 32, Signedness::Unsigned);
477                    IntegerAttr::new(ty, APInt::from_i32(case, bw(32)))
478                });
479                switch_op.set_attr_cases(scope.ctx(), cases);
480                scope.register(&switch_op);
481                scope.update_flags_after_branch(&children);
482            }
483        }
484    }
485}
486
487pub fn match_expand<T: CubeEnum>(
488    scope: &Scope,
489    value: T,
490    discriminant0: i32,
491    arm0: impl FnOnce(&Scope, T::RuntimeValue),
492) -> MatchExpand<T> {
493    let discriminant = value.discriminant();
494    match discriminant.constant() {
495        Some(const_variant) if const_variant.as_i32() == discriminant0 => {
496            let runtime_value = value.runtime_value();
497            arm0(scope, runtime_value.clone_unchecked());
498            MatchExpand::ComptimeVariant {
499                variant: const_variant.as_i32(),
500                runtime_value,
501                matched: true,
502            }
503        }
504        Some(const_variant) => MatchExpand::ComptimeVariant {
505            variant: const_variant.as_i32(),
506            runtime_value: value.runtime_value(),
507            matched: false,
508        },
509        None => {
510            let discriminant = discriminant.read_value(scope);
511            let runtime_value = value.runtime_value();
512
513            let switch_op = SwitchOp::new(scope.ctx_mut(), discriminant);
514            let body = switch_op.append_case_block(scope.ctx_mut());
515            let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
516            arm0(&case_child, runtime_value.clone_unchecked());
517            case_child.terminate_yield();
518
519            MatchExpand::RuntimeVariant {
520                switch_op,
521                cases: alloc::vec![discriminant0],
522                children: alloc::vec![case_child],
523                has_default: false,
524                runtime_value,
525            }
526        }
527    }
528}
529
530#[allow(clippy::large_enum_variant)]
531pub enum MatchExpandExpr<T: CubeEnum, C: Assign> {
532    ComptimeVariant {
533        variant: i32,
534        runtime_value: T::RuntimeValue,
535        out: Option<C>,
536        matched: bool,
537    },
538    RuntimeVariant {
539        switch_op: SwitchOp,
540        cases: Vec<i32>,
541        children: Vec<Scope>,
542        has_default: bool,
543        out: C,
544        runtime_value: T::RuntimeValue,
545    },
546}
547
548impl<T: CubeEnum, C: Assign> MatchExpandExpr<T, C> {
549    pub fn case<R: RuntimeAssign<Expand = C>>(
550        mut self,
551        scope: &Scope,
552        value: i32,
553        block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
554    ) -> Self {
555        match &mut self {
556            Self::RuntimeVariant {
557                switch_op,
558                cases,
559                children,
560                out,
561                runtime_value,
562                ..
563            } => {
564                cases.push(value);
565                let body = switch_op.append_case_block(scope.ctx_mut());
566                let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
567                let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
568                out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
569                case_child.terminate_yield();
570                children.push(case_child);
571            }
572            Self::ComptimeVariant {
573                variant,
574                runtime_value,
575                out,
576                matched,
577            } => {
578                if value == *variant {
579                    *out =
580                        Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
581                    *matched = true;
582                }
583            }
584        }
585        self
586    }
587
588    pub fn default<R: RuntimeAssign<Expand = C>>(
589        mut self,
590        scope: &Scope,
591        block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
592    ) -> Self {
593        match &mut self {
594            Self::RuntimeVariant {
595                switch_op,
596                children,
597                runtime_value,
598                out,
599                has_default,
600                ..
601            } => {
602                let body = switch_op.default_block(scope.ctx());
603                let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
604                let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
605                out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
606                case_child.terminate_yield();
607                children.push(case_child);
608                *has_default = true;
609            }
610            Self::ComptimeVariant {
611                runtime_value,
612                out,
613                matched,
614                ..
615            } => {
616                if !*matched {
617                    *out =
618                        Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
619                    *matched = true;
620                }
621            }
622        }
623        self
624    }
625
626    pub fn finish(self, scope: &Scope) -> C {
627        match self {
628            MatchExpandExpr::ComptimeVariant { out, .. } => {
629                out.expect("At least one variant should be matched")
630            }
631            MatchExpandExpr::RuntimeVariant {
632                switch_op,
633                cases,
634                children,
635                has_default,
636                out,
637                ..
638            } => {
639                if !has_default {
640                    let default_body = switch_op.default_block(scope.ctx());
641                    let mut inserter = OpInserter::new_at_block_end(default_body);
642                    let unreachable = UnreachableOp::new(scope.ctx_mut());
643                    inserter.append_op(scope.ctx(), &unreachable);
644                }
645
646                let cases = cases.into_iter().map(|case| {
647                    let ty = IntegerType::get(scope.ctx(), 32, Signedness::Unsigned);
648                    IntegerAttr::new(ty, APInt::from_i32(case, bw(32)))
649                });
650                switch_op.set_attr_cases(scope.ctx(), cases);
651                scope.register(&switch_op);
652                scope.update_flags_after_branch(&children);
653
654                out
655            }
656        }
657    }
658}
659
660pub fn match_expand_expr<T: CubeEnum, C: RuntimeAssign>(
661    scope: &Scope,
662    value: T,
663    discriminant0: i32,
664    arm0: impl FnOnce(&Scope, T::RuntimeValue) -> C,
665) -> MatchExpandExpr<T, C::Expand> {
666    let discriminant = value.discriminant();
667    match discriminant.constant() {
668        Some(const_variant) if const_variant.as_i32() == discriminant0 => {
669            let runtime_value = value.runtime_value();
670            let out = arm0(scope, runtime_value.clone_unchecked());
671            MatchExpandExpr::ComptimeVariant {
672                variant: const_variant.as_i32(),
673                out: Some(out.into_expand(scope)),
674                runtime_value,
675                matched: true,
676            }
677        }
678        Some(const_variant) => MatchExpandExpr::ComptimeVariant {
679            variant: const_variant.as_i32(),
680            out: None,
681            runtime_value: value.runtime_value(),
682            matched: false,
683        },
684        None => {
685            let discriminant = discriminant.read_value(scope);
686            let runtime_value = value.runtime_value();
687
688            let switch_op = SwitchOp::new(scope.ctx_mut(), discriminant);
689            let body = switch_op.append_case_block(scope.ctx_mut());
690            let case_child = scope.branch_child(OpInserter::new_at_block_end(body));
691            let ret_val = arm0(&case_child, runtime_value.clone_unchecked());
692
693            let mut out = ret_val.init_mut(scope);
694            out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
695            case_child.terminate_yield();
696
697            MatchExpandExpr::RuntimeVariant {
698                switch_op,
699                out,
700                cases: alloc::vec![discriminant0],
701                children: alloc::vec![case_child],
702                runtime_value,
703                has_default: false,
704            }
705        }
706    }
707}
708
709pub fn continue_expand(scope: &Scope) {
710    let inv_continue_flag = scope
711        .expand_state()
712        .inv_continue_flag
713        .expect("Should be in loop");
714    let false_ = false.__expand_runtime_method(scope).expand;
715    assign::expand_element(scope, false_, inv_continue_flag.into());
716    scope.expand_state_mut().may_continue = true;
717}
718
719pub fn break_expand(scope: &Scope) {
720    let inv_break_flag = scope
721        .expand_state()
722        .inv_break_flag
723        .expect("Should be in loop");
724    let false_ = false.__expand_runtime_method(scope).expand;
725    assign::expand_element(scope, false_, inv_break_flag.into());
726    scope.expand_state_mut().may_break = true;
727}
728
729pub fn return_expand(scope: &Scope) {
730    let inv_return_flag = scope.expand_state().inv_return_flag;
731    if let Some(inv_return_flag) = inv_return_flag {
732        let false_ = false.__expand_runtime_method(scope).expand;
733        assign::expand_element(scope, false_, inv_return_flag.into());
734        scope.expand_state_mut().may_return = true;
735    } else {
736        panic!("Can't return when no return flag has been set up")
737    }
738}
739
740/// This is a separate function because otherwise Rust can't infer the generic
741pub fn return_with_value_expand<T: CubePrimitive>(
742    scope: &Scope,
743    value: NativeExpand<T>,
744) -> NativeExpand<T> {
745    let inv_return_flag = scope.expand_state().inv_return_flag;
746    if let Some(inv_return_flag) = inv_return_flag {
747        let return_value = scope.expand_state().return_value.expect("Should exist");
748        let false_ = false.__expand_runtime_method(scope).expand;
749
750        assign::expand_element(scope, false_, inv_return_flag.into());
751        assign::expand_element(scope, value.expand, return_value.into());
752        scope.expand_state_mut().may_return = true;
753    } else {
754        panic!("Can't return when no return flag has been set up")
755    }
756    value
757}
758
759pub fn terminate_expand(scope: &Scope) {
760    let inv_terminate_flag = scope.expand_state().inv_terminate_flag;
761    if let Some(inv_terminate_flag) = inv_terminate_flag {
762        let false_ = false.__expand_runtime_method(scope).expand;
763        assign::expand_element(scope, false_, inv_terminate_flag.into());
764        scope.expand_state_mut().may_terminate = true;
765    } else {
766        panic!("Can't terminate when no terminate flag has been set up")
767    }
768}
769
770#[diagnostic::on_unimplemented(
771    message = "Only runtime primitives can be returned. {Self} is not a primitive."
772)]
773pub trait EarlyReturnable {
774    fn init_value(scope: &Scope) -> Option<Value>;
775    fn from_value(scope: &Scope, value: Option<Value>) -> Self;
776}
777
778impl<T: CubePrimitive> EarlyReturnable for NativeExpand<T> {
779    fn init_value(scope: &Scope) -> Option<Value> {
780        Some(init_mut_of_type(scope, T::__expand_as_type(scope)).value(scope))
781    }
782    fn from_value(scope: &Scope, value: Option<Value>) -> Self {
783        read_value(scope, value.unwrap()).into()
784    }
785}
786impl EarlyReturnable for () {
787    fn init_value(_: &Scope) -> Option<Value> {
788        None
789    }
790    fn from_value(_: &Scope, _: Option<Value>) -> Self {}
791}
792
793pub fn setup_early_return<T: EarlyReturnable>(scope: &Scope, body: impl FnOnce(&Scope) -> T) -> T {
794    let value = T::init_value(scope);
795    let inserter = OpInserter::new(scope.inserter().get_insertion_point());
796    let child = scope.inlined_func_child(inserter, value);
797    body(&child);
798
799    let current_block = scope.inserter().get_insertion_block(scope.ctx()).unwrap();
800    let after_point = op_insertion_point_in_block(
801        scope.ctx(),
802        child.inserter().get_insertion_point(),
803        current_block,
804    );
805    if child.inserter().get_insertion_block(scope.ctx()) != Some(current_block) {
806        child.terminate_yield();
807    }
808
809    scope.inserter().set_insertion_point(after_point);
810    scope.update_flags_after_early_return(&[child]);
811    T::from_value(scope, value)
812}
813
814pub mod unreachable_unchecked {
815    use super::*;
816
817    pub fn expand(scope: &Scope) {
818        scope.register(&UnreachableOp::new(scope.ctx_mut()));
819    }
820}
821
822/// `WhileOp` builder. Needs to work like this for borrow checker reasons.
823/// Rust requires all closures passed to a function to be live at once, so we use a builder pattern
824/// and separate the cond and body into separate functions to allow the borrows to work.
825pub struct WhileBuilder {
826    while_op: WhileOp,
827    cond: ExpandValue,
828    cond_scope: Scope,
829}
830
831impl WhileBuilder {
832    pub fn new(scope: &Scope, mut cond: impl FnMut(&Scope) -> NativeExpand<bool>) -> Self {
833        let while_op = WhileOp::new(scope.ctx_mut());
834        let cond_scope = scope.branch_child(OpInserter::new_at_block_start(
835            while_op.before_block(scope.ctx()),
836        ));
837
838        WhileBuilder {
839            while_op,
840            cond: cond(&cond_scope).expand,
841            cond_scope,
842        }
843    }
844
845    pub fn with_body(self, scope: &Scope, mut block: impl FnMut(&Scope)) {
846        let Self {
847            while_op,
848            mut cond,
849            cond_scope,
850        } = self;
851
852        let body = while_op.after_block(scope.ctx());
853        let body = scope.loop_child(OpInserter::new_at_block_end(body));
854        block(&body);
855        body.terminate_yield();
856
857        let expand_state = *body.expand_state();
858        let break_flag = expand_state.inv_break_flag.unwrap().into();
859        let return_flag = expand_state.inv_return_flag.map(Into::into);
860        let terminate_flag = expand_state.inv_terminate_flag.map(Into::into);
861
862        if expand_state.may_break {
863            cond = binary_expand(&cond_scope, cond, break_flag, BoolAndOp::new);
864        }
865        if expand_state.may_return {
866            let return_flag = return_flag.unwrap();
867            cond = binary_expand(&cond_scope, cond, return_flag, BoolAndOp::new);
868        }
869        if expand_state.may_terminate {
870            let terminate_flag = terminate_flag.unwrap();
871            cond = binary_expand(&cond_scope, cond, terminate_flag, BoolAndOp::new);
872        }
873
874        let cond = cond.read_value(&cond_scope);
875        cond_scope.register(&ConditionOp::new(scope.ctx_mut(), cond));
876
877        scope.register(&while_op);
878        scope.update_flags_after_loop(&[cond_scope, body]);
879    }
880}
881
882/// register a range loop if it contains no break or return, destructure to while if it does
883pub(crate) fn register_range_loop<I: Int>(scope: &Scope, for_op: &RangeLoopOp, body: &Scope) {
884    let ctx = scope.ctx_mut();
885    let ExpandState {
886        may_terminate,
887        may_return,
888        may_break,
889        may_continue,
890        inv_terminate_flag,
891        inv_return_flag,
892        inv_break_flag,
893        inv_continue_flag: _,
894        return_value: _,
895    } = *body.expand_state();
896    if !may_break && !may_return && !may_terminate {
897        body.terminate_yield();
898        scope.register(for_op);
899        return;
900    }
901
902    let mut rewriter = IRRewriter::<DummyListener>::default();
903
904    let start = for_op.start(ctx);
905    let end = for_op.end(ctx);
906    let step = for_op.step(ctx);
907    let iter_var_old = for_op.iter_var(ctx);
908
909    let iter_var = ExpandValue::from(scope.create_local_mut(iter_var_old.get_type(ctx), None));
910    assign::expand_element(scope, start.into(), iter_var);
911
912    // Normally it needs to be predicated on `break;`/`return;`, but not `continue;`. However, this
913    // would require additional branching and Rust actually doesn't allow loop counters to escape
914    // the loop. So just not predicating it is cheaper, easier, and semantically identical. Yay Rust!
915    if may_continue {
916        set_inserter_before_terminator(body.inserter(), ctx, for_op.loop_body(ctx));
917    }
918    assign_binop_expand::<I>(
919        body,
920        &mut iter_var.into(),
921        step.into(),
922        I::__expand_native_add,
923    );
924    body.terminate_yield();
925
926    body.inserter()
927        .set_insertion_point_to_block_start(for_op.loop_body(ctx));
928    let iter_val = iter_var.read_value(body);
929    rewriter.replace_value_uses_with(ctx, iter_var_old, iter_val);
930    BasicBlock::remove_argument(for_op.loop_body(ctx), ctx, 0);
931
932    let while_op = WhileOp::new(ctx);
933
934    let cond_scope = scope.branch_child(OpInserter::new_at_block_start(
935        while_op.before_block(scope.ctx()),
936    ));
937
938    let mut cond = I::__expand_native_lt(&cond_scope, iter_var, end.into());
939    if may_break {
940        let inv_break_flag = inv_break_flag.unwrap().into();
941        cond = binary_expand(&cond_scope, cond, inv_break_flag, BoolAndOp::new);
942    }
943    if may_return {
944        let inv_return_flag = inv_return_flag.unwrap().into();
945        cond = binary_expand(&cond_scope, cond, inv_return_flag, BoolAndOp::new);
946    }
947    if may_terminate {
948        let inv_terminate_flag = inv_terminate_flag.unwrap().into();
949        cond = binary_expand(&cond_scope, cond, inv_terminate_flag, BoolAndOp::new);
950    }
951
952    let cond = cond.read_value(&cond_scope);
953    cond_scope.register(&ConditionOp::new(scope.ctx_mut(), cond));
954
955    rewriter.erase_region(ctx, while_op.after_region(ctx));
956    Region::move_to_op(for_op.get_region(ctx), while_op.get_operation(), ctx);
957    rewriter.erase_operation(ctx, for_op.get_operation());
958
959    scope.register(&while_op);
960}
961
962// Don't make this `FnOnce`, it must be executable multiple times
963pub fn loop_expand(scope: &Scope, block: impl FnMut(&Scope)) {
964    WhileBuilder::new(scope, |scope| true.__expand_runtime_method(scope)).with_body(scope, block);
965}