1use alloc::vec::Vec;
2use cubecl_ir::{
3 ExpandState, ExpandValue, OpInserter,
4 dialect::{
5 branch::{ConditionOp, IfOp, RangeLoopOp, ReturnOp, SwitchOp, UnreachableOp, WhileOp},
6 general::BoolAndOp,
7 },
8 pliron::{irbuild::inserter::Inserter, r#type::TypedHandle},
9};
10use pliron::{
11 basic_block::BasicBlock,
12 builtin::{
13 attributes::IntegerAttr,
14 op_interfaces::OneRegionInterface,
15 types::{IntegerType, Signedness},
16 },
17 irbuild::{
18 listener::DummyListener,
19 rewriter::{IRRewriter, Rewriter},
20 },
21 op::Op,
22 region::Region,
23 r#type::Typed,
24 utils::apint::{APInt, bw},
25};
26
27use crate::{
28 IntoRuntime,
29 frontend::{ReadValue, RuntimeAssign, assign, assign_binop_expand, binary_expand},
30 prelude::{CubeEnum, ExpandTypeClone},
31};
32use crate::{ir::Scope, prelude::Assign};
33
34use super::{Int, NativeExpand};
35
36pub trait Iterable: Sized {
39 type Item;
40
41 fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, Self::Item));
51 fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, Self::Item));
58 fn const_len(&self) -> Option<usize> {
60 None
61 }
62}
63
64pub fn for_expand<I: Iterable>(
75 scope: &Scope,
76 range: I,
77 unroll: bool,
78 mut body: impl FnMut(&Scope, I::Item),
79) {
80 for_expand_dyn(scope, range, unroll, &mut body);
81}
82
83fn for_expand_dyn<I: Iterable>(
84 scope: &Scope,
85 range: I,
86 unroll: bool,
87 body: &mut dyn FnMut(&Scope, I::Item),
88) {
89 if unroll || range.const_len() == Some(1) {
90 range.expand_unroll(scope, body);
91 } else {
92 range.expand(scope, body);
93 }
94}
95
96pub fn if_expand(scope: &Scope, condition: NativeExpand<bool>, block: impl FnOnce(&Scope)) {
97 let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
98 match comptime_cond {
99 Some(cond) => {
100 if cond {
101 block(scope);
102 }
103 }
104 None => {
105 let cond = condition.read_value(scope);
106 let if_op = IfOp::new(scope.ctx_mut(), cond);
107
108 let then_block = if_op.then_block(scope.ctx());
109 let then_child = scope.child(OpInserter::new_at_block_end(then_block));
110 block(&then_child);
111 then_child.terminate_yield();
112
113 let else_block = if_op.else_block(scope.ctx());
114 let else_child = scope.child(OpInserter::new_at_block_end(else_block));
115 else_child.terminate_yield();
116
117 scope.register(&if_op);
118 scope.set_break_return(&[then_child, else_child]);
119 }
120 }
121}
122
123#[allow(clippy::large_enum_variant)]
124pub enum IfElseExpand {
125 ComptimeThen,
126 ComptimeElse,
127 Runtime {
128 runtime_cond: NativeExpand<bool>,
129 if_op: IfOp,
130 then_child: Scope,
131 },
132}
133
134impl IfElseExpand {
135 pub fn or_else(self, scope: &Scope, else_block: impl FnOnce(&Scope)) {
136 match self {
137 Self::Runtime {
138 if_op, then_child, ..
139 } => {
140 let else_body = if_op.else_block(scope.ctx());
141 let else_child = scope.child(OpInserter::new_at_block_end(else_body));
142 else_block(&else_child);
143 else_child.terminate_yield();
144
145 scope.register(&if_op);
146 scope.set_break_return(&[then_child, else_child]);
147 }
148 Self::ComptimeElse => else_block(scope),
149 Self::ComptimeThen => (),
150 }
151 }
152}
153
154pub fn if_else_expand(
155 scope: &Scope,
156 condition: NativeExpand<bool>,
157 then_block: impl FnOnce(&Scope),
158) -> IfElseExpand {
159 let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
160 match comptime_cond {
161 Some(true) => {
162 then_block(scope);
163 IfElseExpand::ComptimeThen
164 }
165 Some(false) => IfElseExpand::ComptimeElse,
166 None => {
167 let cond = condition.read_value(scope);
168 let if_op = IfOp::new(scope.ctx_mut(), cond);
169 let if_block = if_op.then_block(scope.ctx());
170 let then_child = scope.child(OpInserter::new_at_block_end(if_block));
171 then_block(&then_child);
172 then_child.terminate_yield();
173
174 IfElseExpand::Runtime {
175 runtime_cond: condition,
176 if_op,
177 then_child,
178 }
179 }
180 }
181}
182
183#[allow(clippy::large_enum_variant)]
184pub enum IfElseExprExpand<C: Assign> {
185 ComptimeThen(C),
186 ComptimeElse,
187 Runtime {
188 runtime_cond: NativeExpand<bool>,
189 out: C,
190 if_op: IfOp,
191 then_child: Scope,
192 },
193}
194
195impl<C: Assign> IfElseExprExpand<C> {
196 pub fn or_else<R: RuntimeAssign<Expand = C>>(
197 self,
198 scope: &Scope,
199 else_block: impl FnOnce(&Scope) -> R,
200 ) -> C {
201 match self {
202 Self::Runtime {
203 mut out,
204 if_op,
205 then_child,
206 ..
207 } => {
208 let else_body = if_op.else_block(scope.ctx());
209 let else_child = scope.child(OpInserter::new_at_block_end(else_body));
210 let ret = else_block(&else_child);
211 out.__expand_assign_method(&else_child, ret.into_expand(scope));
212 else_child.terminate_yield();
213
214 scope.register(&if_op);
215 scope.set_break_return(&[then_child, else_child]);
216 out
217 }
218 Self::ComptimeElse => else_block(scope).into_expand(scope),
219 Self::ComptimeThen(ret) => ret,
220 }
221 }
222}
223
224pub fn if_else_expr_expand<C: RuntimeAssign>(
225 scope: &Scope,
226 condition: NativeExpand<bool>,
227 then_block: impl FnOnce(&Scope) -> C,
228) -> IfElseExprExpand<C::Expand> {
229 let comptime_cond = condition.expand.as_const().map(|it| it.as_bool());
230 match comptime_cond {
231 Some(true) => {
232 let ret = then_block(scope);
233 IfElseExprExpand::ComptimeThen(ret.into_expand(scope))
234 }
235 Some(false) => IfElseExprExpand::ComptimeElse,
236 None => {
237 let cond = condition.read_value(scope);
238 let if_op = IfOp::new(scope.ctx_mut(), cond);
239 let then_body = if_op.then_block(scope.ctx());
240 let then_child = scope.child(OpInserter::new_at_block_end(then_body));
241 let ret = then_block(&then_child);
242 let mut out = ret.init_mut(scope);
243 out.__expand_assign_method(&then_child, ret.into_expand(scope));
244 then_child.terminate_yield();
245
246 IfElseExprExpand::Runtime {
247 runtime_cond: condition,
248 out,
249 if_op,
250 then_child,
251 }
252 }
253 }
254}
255
256pub struct SwitchExpand<I: Int> {
257 switch_op: SwitchOp,
258 cases: Vec<I>,
259 children: Vec<Scope>,
260}
261
262impl<I: Int> SwitchExpand<I> {
263 pub fn case(mut self, scope: &Scope, value: impl Int, block: impl FnOnce(&Scope)) -> Self {
264 let value = I::from(value).unwrap();
265 self.cases.push(value);
266 let body = self.switch_op.append_case_block(scope.ctx_mut());
267 let case_child = scope.child(OpInserter::new_at_block_end(body));
268 block(&case_child);
269 case_child.terminate_yield();
270 self.children.push(case_child);
271 self
272 }
273
274 pub fn finish(self, scope: &Scope) {
275 let cases = self.cases.into_iter().map(|case| {
276 let ty = I::__expand_as_type(scope);
277 let ty = TypedHandle::<IntegerType>::from_handle(ty, scope.ctx()).unwrap();
278 let width = bw(ty.deref(scope.ctx()).width() as usize);
279 IntegerAttr::new(ty, APInt::from_i128(case.to_i128().unwrap(), width))
280 });
281 self.switch_op.set_attr_cases(scope.ctx(), cases);
282 scope.register(&self.switch_op);
283 scope.set_break_return(&self.children);
284 }
285}
286
287pub fn switch_expand<I: Int>(
288 scope: &Scope,
289 value: NativeExpand<I>,
290 default_block: impl FnOnce(&Scope),
291) -> SwitchExpand<I> {
292 let value = value.read_value(scope);
293 let switch_op = SwitchOp::new(scope.ctx_mut(), value);
294
295 let default_body = switch_op.default_block(scope.ctx());
296 let default_child = scope.child(OpInserter::new_at_block_end(default_body));
297 default_block(&default_child);
298 default_child.terminate_yield();
299
300 SwitchExpand {
301 switch_op,
302 cases: Vec::new(),
303 children: alloc::vec![default_child],
304 }
305}
306
307pub struct SwitchExpandExpr<I: Int, C: Assign> {
308 switch_op: SwitchOp,
309 cases: Vec<I>,
310 children: Vec<Scope>,
311 out: C,
312}
313
314impl<I: Int, C: Assign> SwitchExpandExpr<I, C> {
315 pub fn case<T: RuntimeAssign<Expand = C>>(
316 mut self,
317 scope: &Scope,
318 value: impl Int,
319 block: impl FnOnce(&Scope) -> T,
320 ) -> Self {
321 let value = I::from(value).unwrap();
322 self.cases.push(value);
323 let body = self.switch_op.append_case_block(scope.ctx_mut());
324 let case_child = scope.child(OpInserter::new_at_block_end(body));
325 let ret = block(&case_child);
326 self.out
327 .__expand_assign_method(&case_child, ret.into_expand(scope));
328 case_child.terminate_yield();
329 self.children.push(case_child);
330 self
331 }
332
333 pub fn finish(self, scope: &Scope) -> C {
334 let cases = self.cases.into_iter().map(|case| {
335 let ty = I::__expand_as_type(scope);
336 let ty = TypedHandle::<IntegerType>::from_handle(ty, scope.ctx()).unwrap();
337 let width = bw(ty.deref(scope.ctx()).width() as usize);
338 IntegerAttr::new(ty, APInt::from_i128(case.to_i128().unwrap(), width))
339 });
340 self.switch_op.set_attr_cases(scope.ctx(), cases);
341 scope.register(&self.switch_op);
342 scope.set_break_return(&self.children);
343 self.out
344 }
345}
346
347pub fn switch_expand_expr<I: Int, C: RuntimeAssign>(
348 scope: &Scope,
349 value: NativeExpand<I>,
350 default_block: impl FnOnce(&Scope) -> C,
351) -> SwitchExpandExpr<I, C::Expand> {
352 let value = value.read_value(scope);
353 let switch_op = SwitchOp::new(scope.ctx_mut(), value);
354
355 let default_body = switch_op.default_block(scope.ctx());
356 let default_child = scope.child(OpInserter::new_at_block_end(default_body));
357 let default = default_block(&default_child);
358 let mut out = default.init_mut(scope);
359 out.__expand_assign_method(&default_child, default.into_expand(scope));
360 default_child.terminate_yield();
361
362 SwitchExpandExpr {
363 switch_op,
364 cases: Vec::new(),
365 children: alloc::vec![default_child],
366 out,
367 }
368}
369
370#[allow(clippy::large_enum_variant)]
371pub enum MatchExpand<T: CubeEnum> {
372 ComptimeVariant {
373 variant: i32,
374 runtime_value: T::RuntimeValue,
375 matched: bool,
376 },
377 RuntimeVariant {
378 switch_op: SwitchOp,
379 cases: Vec<i32>,
380 children: Vec<Scope>,
381 has_default: bool,
382 runtime_value: T::RuntimeValue,
383 },
384}
385
386impl<T: CubeEnum> MatchExpand<T> {
387 pub fn case(
388 mut self,
389 scope: &Scope,
390 value: i32,
391 block: impl FnOnce(&Scope, T::RuntimeValue),
392 ) -> Self {
393 match &mut self {
394 Self::RuntimeVariant {
395 switch_op,
396 cases,
397 children,
398 runtime_value,
399 ..
400 } => {
401 cases.push(value);
402 let body = switch_op.append_case_block(scope.ctx_mut());
403 let case_child = scope.child(OpInserter::new_at_block_end(body));
404 block(&case_child, (*runtime_value).clone_unchecked());
405 case_child.terminate_yield();
406 children.push(case_child);
407 }
408 Self::ComptimeVariant {
409 variant,
410 runtime_value,
411 matched,
412 } => {
413 if value == *variant {
414 block(scope, (*runtime_value).clone_unchecked());
415 *matched = true;
416 }
417 }
418 }
419 self
420 }
421
422 pub fn default(mut self, scope: &Scope, block: impl FnOnce(&Scope, T::RuntimeValue)) -> Self {
423 match &mut self {
424 Self::RuntimeVariant {
425 switch_op,
426 children,
427 runtime_value,
428 has_default,
429 ..
430 } => {
431 let body = switch_op.default_block(scope.ctx());
432 let case_child = scope.child(OpInserter::new_at_block_end(body));
433 block(&case_child, (*runtime_value).clone_unchecked());
434 case_child.terminate_yield();
435 children.push(case_child);
436 *has_default = true;
437 }
438 Self::ComptimeVariant {
439 runtime_value,
440 matched,
441 ..
442 } => {
443 if !*matched {
444 block(scope, (*runtime_value).clone_unchecked());
445 *matched = true;
446 }
447 }
448 }
449 self
450 }
451
452 pub fn finish(self, scope: &Scope) {
453 match self {
454 MatchExpand::ComptimeVariant { .. } => {}
455 MatchExpand::RuntimeVariant {
456 switch_op,
457 cases,
458 children,
459 has_default,
460 ..
461 } => {
462 if !has_default {
463 let default_body = switch_op.default_block(scope.ctx());
464 let mut inserter = OpInserter::new_at_block_end(default_body);
465 let unreachable = UnreachableOp::new(scope.ctx_mut());
466 inserter.append_op(scope.ctx(), &unreachable);
467 }
468
469 let cases = cases.into_iter().map(|case| {
470 let ty = IntegerType::get(scope.ctx(), 32, Signedness::Unsigned);
471 IntegerAttr::new(ty, APInt::from_i32(case, bw(32)))
472 });
473 switch_op.set_attr_cases(scope.ctx(), cases);
474 scope.register(&switch_op);
475 scope.set_break_return(&children);
476 }
477 }
478 }
479}
480
481pub fn match_expand<T: CubeEnum>(
482 scope: &Scope,
483 value: T,
484 discriminant0: i32,
485 arm0: impl FnOnce(&Scope, T::RuntimeValue),
486) -> MatchExpand<T> {
487 let discriminant = value.discriminant();
488 match discriminant.constant() {
489 Some(const_variant) if const_variant.as_i32() == discriminant0 => {
490 let runtime_value = value.runtime_value();
491 arm0(scope, runtime_value.clone_unchecked());
492 MatchExpand::ComptimeVariant {
493 variant: const_variant.as_i32(),
494 runtime_value,
495 matched: true,
496 }
497 }
498 Some(const_variant) => MatchExpand::ComptimeVariant {
499 variant: const_variant.as_i32(),
500 runtime_value: value.runtime_value(),
501 matched: false,
502 },
503 None => {
504 let discriminant = discriminant.read_value(scope);
505 let runtime_value = value.runtime_value();
506
507 let switch_op = SwitchOp::new(scope.ctx_mut(), discriminant);
508 let body = switch_op.append_case_block(scope.ctx_mut());
509 let case_child = scope.child(OpInserter::new_at_block_end(body));
510 arm0(&case_child, runtime_value.clone_unchecked());
511 case_child.terminate_yield();
512
513 MatchExpand::RuntimeVariant {
514 switch_op,
515 cases: alloc::vec![discriminant0],
516 children: alloc::vec![case_child],
517 has_default: false,
518 runtime_value,
519 }
520 }
521 }
522}
523
524#[allow(clippy::large_enum_variant)]
525pub enum MatchExpandExpr<T: CubeEnum, C: Assign> {
526 ComptimeVariant {
527 variant: i32,
528 runtime_value: T::RuntimeValue,
529 out: Option<C>,
530 matched: bool,
531 },
532 RuntimeVariant {
533 switch_op: SwitchOp,
534 cases: Vec<i32>,
535 children: Vec<Scope>,
536 has_default: bool,
537 out: C,
538 runtime_value: T::RuntimeValue,
539 },
540}
541
542impl<T: CubeEnum, C: Assign> MatchExpandExpr<T, C> {
543 pub fn case<R: RuntimeAssign<Expand = C>>(
544 mut self,
545 scope: &Scope,
546 value: i32,
547 block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
548 ) -> Self {
549 match &mut self {
550 Self::RuntimeVariant {
551 switch_op,
552 cases,
553 children,
554 out,
555 runtime_value,
556 ..
557 } => {
558 cases.push(value);
559 let body = switch_op.append_case_block(scope.ctx_mut());
560 let case_child = scope.child(OpInserter::new_at_block_end(body));
561 let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
562 out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
563 case_child.terminate_yield();
564 children.push(case_child);
565 }
566 Self::ComptimeVariant {
567 variant,
568 runtime_value,
569 out,
570 matched,
571 } => {
572 if value == *variant {
573 *out =
574 Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
575 *matched = true;
576 }
577 }
578 }
579 self
580 }
581
582 pub fn default<R: RuntimeAssign<Expand = C>>(
583 mut self,
584 scope: &Scope,
585 block: impl FnOnce(&Scope, T::RuntimeValue) -> R,
586 ) -> Self {
587 match &mut self {
588 Self::RuntimeVariant {
589 switch_op,
590 children,
591 runtime_value,
592 out,
593 has_default,
594 ..
595 } => {
596 let body = switch_op.default_block(scope.ctx());
597 let case_child = scope.child(OpInserter::new_at_block_end(body));
598 let ret_val = block(&case_child, (*runtime_value).clone_unchecked());
599 out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
600 case_child.terminate_yield();
601 children.push(case_child);
602 *has_default = true;
603 }
604 Self::ComptimeVariant {
605 runtime_value,
606 out,
607 matched,
608 ..
609 } => {
610 if !*matched {
611 *out =
612 Some(block(scope, (*runtime_value).clone_unchecked()).into_expand(scope));
613 *matched = true;
614 }
615 }
616 }
617 self
618 }
619
620 pub fn finish(self, scope: &Scope) -> C {
621 match self {
622 MatchExpandExpr::ComptimeVariant { out, .. } => {
623 out.expect("At least one variant should be matched")
624 }
625 MatchExpandExpr::RuntimeVariant {
626 switch_op,
627 cases,
628 children,
629 has_default,
630 out,
631 ..
632 } => {
633 if !has_default {
634 let default_body = switch_op.default_block(scope.ctx());
635 let mut inserter = OpInserter::new_at_block_end(default_body);
636 let unreachable = UnreachableOp::new(scope.ctx_mut());
637 inserter.append_op(scope.ctx(), &unreachable);
638 }
639
640 let cases = cases.into_iter().map(|case| {
641 let ty = IntegerType::get(scope.ctx(), 32, Signedness::Unsigned);
642 IntegerAttr::new(ty, APInt::from_i32(case, bw(32)))
643 });
644 switch_op.set_attr_cases(scope.ctx(), cases);
645 scope.register(&switch_op);
646 scope.set_break_return(&children);
647
648 out
649 }
650 }
651 }
652}
653
654pub fn match_expand_expr<T: CubeEnum, C: RuntimeAssign>(
655 scope: &Scope,
656 value: T,
657 discriminant0: i32,
658 arm0: impl FnOnce(&Scope, T::RuntimeValue) -> C,
659) -> MatchExpandExpr<T, C::Expand> {
660 let discriminant = value.discriminant();
661 match discriminant.constant() {
662 Some(const_variant) if const_variant.as_i32() == discriminant0 => {
663 let runtime_value = value.runtime_value();
664 let out = arm0(scope, runtime_value.clone_unchecked());
665 MatchExpandExpr::ComptimeVariant {
666 variant: const_variant.as_i32(),
667 out: Some(out.into_expand(scope)),
668 runtime_value,
669 matched: true,
670 }
671 }
672 Some(const_variant) => MatchExpandExpr::ComptimeVariant {
673 variant: const_variant.as_i32(),
674 out: None,
675 runtime_value: value.runtime_value(),
676 matched: false,
677 },
678 None => {
679 let discriminant = discriminant.read_value(scope);
680 let runtime_value = value.runtime_value();
681
682 let switch_op = SwitchOp::new(scope.ctx_mut(), discriminant);
683 let body = switch_op.append_case_block(scope.ctx_mut());
684 let case_child = scope.child(OpInserter::new_at_block_end(body));
685 let ret_val = arm0(&case_child, runtime_value.clone_unchecked());
686
687 let mut out = ret_val.init_mut(scope);
688 out.__expand_assign_method(&case_child, ret_val.into_expand(scope));
689 case_child.terminate_yield();
690
691 MatchExpandExpr::RuntimeVariant {
692 switch_op,
693 out,
694 cases: alloc::vec![discriminant0],
695 children: alloc::vec![case_child],
696 runtime_value,
697 has_default: false,
698 }
699 }
700 }
701}
702
703pub fn break_expand(scope: &Scope) {
704 let inv_break_flag = scope
705 .expand_state()
706 .inv_break_flag
707 .expect("Should be in loop");
708 let false_ = false.__expand_runtime_method(scope).expand;
709 assign::expand_element(scope, false_, inv_break_flag.into());
710 scope.expand_state_mut().may_break = true;
711}
712
713pub fn return_expand(scope: &Scope) {
714 let inv_return_flag = scope.expand_state().inv_return_flag;
715 if let Some(inv_return_flag) = inv_return_flag {
716 let false_ = false.__expand_runtime_method(scope).expand;
717 assign::expand_element(scope, false_, inv_return_flag.into());
718 scope.expand_state_mut().may_return = true;
719 } else {
720 scope.register(&ReturnOp::new(scope.ctx_mut()));
723 }
724}
725
726pub mod unreachable_unchecked {
727 use super::*;
728
729 pub fn expand(scope: &Scope) {
730 scope.register(&UnreachableOp::new(scope.ctx_mut()));
731 }
732}
733
734pub struct WhileBuilder {
738 while_op: WhileOp,
739 cond: ExpandValue,
740 cond_scope: Scope,
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746 use cubecl_ir::{
747 AddressType,
748 settings::{Dim3, ExecutionMode, KernelSettings},
749 };
750
751 #[test]
752 fn while_condition_reloads_mutable_flag_inside_loop() {
753 let scope = Scope::root(KernelSettings::new(
754 Dim3::new_1d(1),
755 ExecutionMode::Unchecked,
756 AddressType::U32,
757 ));
758 let mut flag = true.__expand_runtime_method(&scope).init_mut(&scope);
759 let builder = WhileBuilder::new(&scope, |_| flag);
760 let condition_block = builder.while_op.before_block(scope.ctx());
761 builder.with_body(&scope, |body| {
762 flag.__expand_assign_method(body, false.into());
763 });
764
765 let ctx = scope.ctx();
766 let condition = condition_block.deref(ctx).get_terminator(ctx).unwrap();
767 let flag_read = condition.deref(ctx).get_operand(0);
768 assert_eq!(
769 flag_read.get_defining_block(ctx),
770 Some(condition_block),
771 "the condition must reload the flag each iteration, not before entering the loop",
772 );
773 }
774}
775
776impl WhileBuilder {
777 pub fn new(scope: &Scope, mut cond: impl FnMut(&Scope) -> NativeExpand<bool>) -> Self {
778 let while_op = WhileOp::new(scope.ctx_mut());
779 let cond_scope = scope.child(OpInserter::new_at_block_start(
780 while_op.before_block(scope.ctx()),
781 ));
782
783 WhileBuilder {
784 while_op,
785 cond: cond(&cond_scope).expand,
786 cond_scope,
787 }
788 }
789
790 pub fn with_body(self, scope: &Scope, mut block: impl FnMut(&Scope)) {
791 let Self {
792 while_op,
793 mut cond,
794 cond_scope,
795 } = self;
796
797 let body = while_op.after_block(scope.ctx());
798 let body = scope.loop_child(OpInserter::new_at_block_end(body));
799 block(&body);
800 body.terminate_yield();
801
802 let expand_state = *body.expand_state();
803 let break_flag = expand_state.inv_break_flag.unwrap().into();
804 let return_flag = expand_state.inv_return_flag.map(Into::into);
805
806 if expand_state.may_break {
807 cond = binary_expand(&cond_scope, cond, break_flag, BoolAndOp::new);
808 }
809 if expand_state.may_return {
810 let return_flag = return_flag.unwrap();
811 cond = binary_expand(&cond_scope, cond, return_flag, BoolAndOp::new);
812 }
813
814 let cond = cond.read_value(&cond_scope);
815 cond_scope.register(&ConditionOp::new(scope.ctx_mut(), cond));
816
817 scope.register(&while_op);
818 }
819}
820
821pub(crate) fn register_range_loop<I: Int>(scope: &Scope, for_op: &RangeLoopOp, body: &Scope) {
823 let ctx = scope.ctx_mut();
824 let ExpandState {
825 may_return,
826 may_break,
827 inv_return_flag,
828 inv_break_flag,
829 } = *body.expand_state();
830 if !may_break && !may_return {
831 body.terminate_yield();
832 scope.register(for_op);
833 return;
834 }
835
836 let mut rewriter = IRRewriter::<DummyListener>::default();
837
838 let start = for_op.start(ctx);
839 let end = for_op.end(ctx);
840 let step = for_op.step(ctx);
841 let iter_var_old = for_op.iter_var(ctx);
842
843 let iter_var = ExpandValue::from(scope.create_local_mut(iter_var_old.get_type(ctx), None));
844 assign::expand_element(scope, start.into(), iter_var);
845
846 assign_binop_expand::<I>(
847 body,
848 &mut iter_var.into(),
849 step.into(),
850 I::__expand_native_add,
851 );
852 body.terminate_yield();
853
854 body.inserter()
855 .set_insertion_point_to_block_start(for_op.loop_body(ctx));
856 let iter_val = iter_var.read_value(body);
857 rewriter.replace_value_uses_with(ctx, iter_var_old, iter_val);
858 BasicBlock::remove_argument(for_op.loop_body(ctx), ctx, 0);
859
860 let while_op = WhileOp::new(ctx);
861
862 let cond_scope = scope.child(OpInserter::new_at_block_start(
863 while_op.before_block(scope.ctx()),
864 ));
865
866 let mut cond = I::__expand_native_lt(&cond_scope, iter_var, end.into());
867 if may_break {
868 let inv_break_flag = inv_break_flag.unwrap().into();
869 cond = binary_expand(&cond_scope, cond, inv_break_flag, BoolAndOp::new);
870 }
871 if may_return {
872 let inv_return_flag = inv_return_flag.unwrap().into();
873 cond = binary_expand(&cond_scope, cond, inv_return_flag, BoolAndOp::new);
874 }
875
876 let cond = cond.read_value(&cond_scope);
877 cond_scope.register(&ConditionOp::new(scope.ctx_mut(), cond));
878
879 rewriter.erase_region(ctx, while_op.after_region(ctx));
880 Region::move_to_op(for_op.get_region(ctx), while_op.get_operation(), ctx);
881 rewriter.erase_operation(ctx, for_op.get_operation());
882
883 scope.register(&while_op);
884}
885
886pub fn loop_expand(scope: &Scope, block: impl FnMut(&Scope)) {
888 WhileBuilder::new(scope, |scope| true.__expand_runtime_method(scope)).with_body(scope, block);
889}