@@ -628,15 +628,22 @@ impl FunctionContext<'_> {
628628 let start_index = self . codegen_non_tuple_expression ( & for_expr. start_range ) ?;
629629
630630 self . builder . set_location ( for_expr. end_range_location ) ;
631- let end_index = self . codegen_non_tuple_expression ( & for_expr. end_range ) ?;
631+ let mut end_index = self . codegen_non_tuple_expression ( & for_expr. end_range ) ?;
632632
633633 let range_bound = |id| self . builder . current_function . dfg . get_integer_constant ( id) ;
634634
635635 if let ( Some ( start_constant) , Some ( end_constant) ) =
636636 ( range_bound ( start_index) , range_bound ( end_index) )
637637 {
638- // If we can determine that the loop contains zero iterations then there's no need to codegen the loop.
639- if start_constant >= end_constant {
638+ // For inclusive ranges (e.g., `0..=255`), the loop should run if start <= end.
639+ // For exclusive ranges (e.g., `0..256`), the loop should run if start < end.
640+ // If the condition is false, skip the loop entirely.
641+ let should_skip = if for_expr. inclusive {
642+ start_constant > end_constant
643+ } else {
644+ start_constant >= end_constant
645+ } ;
646+ if should_skip {
640647 return Ok ( Self :: unit_value ( ) ) ;
641648 }
642649 }
@@ -647,11 +654,86 @@ impl FunctionContext<'_> {
647654
648655 // this is the 'i' in `for i in start .. end { block }`
649656 let index_type = Self :: convert_non_tuple_type ( & for_expr. index_type ) ;
650- let loop_index = self . builder . add_block_parameter ( loop_entry, index_type) ;
657+ let loop_index = self . builder . add_block_parameter ( loop_entry, index_type. clone ( ) ) ;
658+
659+ let mut inclusive = for_expr. inclusive ;
660+
661+ // If this is an inclusive for loop, check if the end index is not the maximum value for its type.
662+ // In that case we can generate an exclusive for loop up to `end + 1`, which is simpler than
663+ // the code of an inclusive loop.
664+ if inclusive {
665+ if let Some ( end_constant) =
666+ self . builder . current_function . dfg . get_integer_constant ( end_index)
667+ {
668+ let index_type = index_type. unwrap_numeric ( ) ;
669+ let bit_size = match index_type {
670+ NumericType :: Signed { bit_size } => bit_size - 1 ,
671+ NumericType :: Unsigned { bit_size } => bit_size,
672+ NumericType :: NativeField => panic ! ( "Cannot iterate over Field" ) ,
673+ } ;
674+ let max_value = if bit_size == 128 { u128:: MAX } else { ( 1u128 << bit_size) - 1 } ;
675+
676+ if end_constant. into_numeric_constant ( ) . 0 . to_u128 ( ) < max_value {
677+ let end_constant_plus_one = end_constant. inc ( ) ;
678+ end_index = self . builder . numeric_constant (
679+ end_constant_plus_one. into_numeric_constant ( ) . 0 ,
680+ index_type,
681+ ) ;
682+ inclusive = false ;
683+ }
684+ }
685+ }
686+
687+ // For inclusive ranges we could generate a loop like:
688+ //
689+ // ```noir
690+ // let index = start;
691+ // while start <= end {
692+ // body;
693+ // if index == end { break; }
694+ // index += 1;
695+ // }
696+ // ``
697+ //
698+ // We could do that in order to avoid an overflow at `index += 1` when `end` is the maximum
699+ // value for the range type.
700+ //
701+ // However, an SSA like above breaks some assumptions in the unrolling optimization pass.
702+ //
703+ // Instead, we generate something like this:
704+ //
705+ // ```noir
706+ // let index = start;
707+ // // did_not_hit_break is set to false if a break is hit in the for body
708+ // let did_not_hit_break = true;
709+ // for index in start..end {
710+ // body;
711+ // }
712+ // if start <= end && did_not_hit_break {
713+ // index = end;
714+ // body;
715+ // }
716+ // ```
717+ //
718+ // That is, we generate an exclusive for loop and include an extra final iteration that
719+ // is only executed if the start is less than the end, and if no break was hit in the loop body.
720+ let did_not_hit_break_var = if inclusive {
721+ let did_not_hit_break_var = self . builder . insert_allocate ( Type :: bool ( ) ) ;
722+ let zero = self . builder . numeric_constant ( true , NumericType :: bool ( ) ) ;
723+ self . builder . insert_store ( did_not_hit_break_var, zero) ;
724+ Some ( did_not_hit_break_var)
725+ } else {
726+ None
727+ } ;
651728
652729 // Remember the blocks and variable used in case there are break/continue instructions
653730 // within the loop which need to jump to them.
654- self . enter_loop ( Loop { loop_entry, loop_index : Some ( loop_index) , loop_end } ) ;
731+ self . enter_loop ( Loop {
732+ loop_entry,
733+ loop_index : Some ( loop_index) ,
734+ loop_end,
735+ did_not_hit_break_var,
736+ } ) ;
655737
656738 // Set the location of the initial jmp instruction to the start range. This is the location
657739 // used to issue an error if the start range cannot be determined at compile-time.
@@ -661,7 +743,7 @@ impl FunctionContext<'_> {
661743 // Compile the loop entry block
662744 self . builder . switch_to_block ( loop_entry) ;
663745
664- // Set the location of the ending Lt instruction and the jmpif back-edge of the loop to the
746+ // Set the location of the ending comparison instruction and the jmpif back-edge of the loop to the
665747 // end range. These are the instructions used to issue an error if the end of the range
666748 // cannot be determined at compile-time.
667749 self . builder . set_location ( for_expr. end_range_location ) ;
@@ -673,14 +755,71 @@ impl FunctionContext<'_> {
673755 self . define ( for_expr. index_variable , loop_index. into ( ) ) ;
674756
675757 let result = self . codegen_expression ( & for_expr. block ) ;
676- self . codegen_unless_break_or_continue ( result, |this, _| {
758+ self . codegen_unless_break_or_continue ( result. clone ( ) , |this, _| {
677759 let new_loop_index = this. make_offset ( loop_index, 1 , true ) ;
678760 this. builder . terminate_with_jmp ( loop_entry, vec ! [ new_loop_index] ) ;
679761 } ) ?;
680762
681763 // Finish by switching back to the end of the loop
682764 self . builder . switch_to_block ( loop_end) ;
683765 self . exit_loop ( ) ;
766+
767+ // Generate the final iteration for inclusive ranges
768+ if let Some ( did_not_hit_break_var) = did_not_hit_break_var {
769+ let final_iteration = self . builder . insert_block ( ) ;
770+ let final_iteration_end = self . builder . insert_block ( ) ;
771+
772+ let did_not_hit_break = self . builder . insert_load ( did_not_hit_break_var, Type :: bool ( ) ) ;
773+ // `start <= end` is equivalent to `!(start > end)`
774+ let end_is_less_than_start =
775+ self . builder . insert_binary ( end_index, BinaryOp :: Lt , start_index) ;
776+ let start_is_less_than_or_equal_to_end =
777+ self . builder . insert_not ( end_is_less_than_start) ;
778+ let should_execute_loop_body = self . builder . insert_binary (
779+ did_not_hit_break,
780+ BinaryOp :: And ,
781+ start_is_less_than_or_equal_to_end,
782+ ) ;
783+ self . builder . terminate_with_jmpif (
784+ should_execute_loop_body,
785+ final_iteration,
786+ final_iteration_end,
787+ ) ;
788+
789+ self . builder . switch_to_block ( final_iteration) ;
790+
791+ // We need to be in the context of a loop because a `break` in the loop, in the final
792+ // iteration, has to jump somewhere.
793+ // We set both `loop_entry` and `loop_end` to `final_iteration_end` because:
794+ // - `break` will jump to `loop_end`
795+ // - `continue` will jump to `loop_entry`, but here we also want to jump to the end
796+ self . enter_loop ( Loop {
797+ loop_entry : final_iteration_end,
798+ loop_index : None ,
799+ loop_end : final_iteration_end,
800+ did_not_hit_break_var : None ,
801+ } ) ;
802+
803+ // Temporarily allow redefinitions because:
804+ // 1. We'll override the index variable
805+ // 2. We'll generate the for loop body again
806+ let old_redefinitions_allowed = self . redefinitions_allowed ;
807+ self . redefinitions_allowed = true ;
808+
809+ self . define ( for_expr. index_variable , end_index. into ( ) ) ;
810+
811+ let result = self . codegen_expression ( & for_expr. block ) ;
812+ self . codegen_unless_break_or_continue ( result, |this, _| {
813+ this. builder . terminate_with_jmp ( final_iteration_end, vec ! [ ] ) ;
814+ } ) ?;
815+
816+ self . redefinitions_allowed = old_redefinitions_allowed;
817+
818+ self . builder . switch_to_block ( final_iteration_end) ;
819+
820+ self . exit_loop ( ) ;
821+ }
822+
684823 Ok ( Self :: unit_value ( ) )
685824 }
686825
@@ -701,7 +840,12 @@ impl FunctionContext<'_> {
701840 let loop_body = self . builder . insert_block ( ) ;
702841 let loop_end = self . builder . insert_block ( ) ;
703842
704- self . enter_loop ( Loop { loop_entry : loop_body, loop_index : None , loop_end } ) ;
843+ self . enter_loop ( Loop {
844+ loop_entry : loop_body,
845+ loop_index : None ,
846+ loop_end,
847+ did_not_hit_break_var : None ,
848+ } ) ;
705849
706850 self . builder . terminate_with_jmp ( loop_body, vec ! [ ] ) ;
707851
@@ -746,7 +890,12 @@ impl FunctionContext<'_> {
746890 let condition = self . codegen_non_tuple_expression ( & while_. condition ) ?;
747891 self . builder . terminate_with_jmpif ( condition, while_body, while_end) ;
748892
749- self . enter_loop ( Loop { loop_entry : while_entry, loop_index : None , loop_end : while_end } ) ;
893+ self . enter_loop ( Loop {
894+ loop_entry : while_entry,
895+ loop_index : None ,
896+ loop_end : while_end,
897+ did_not_hit_break_var : None ,
898+ } ) ;
750899
751900 // Codegen the body
752901 self . builder . switch_to_block ( while_body) ;
@@ -1241,7 +1390,15 @@ impl FunctionContext<'_> {
12411390 }
12421391
12431392 fn codegen_break ( & mut self ) -> Result < Values , RuntimeError > {
1244- let loop_end = self . current_loop ( ) . loop_end ;
1393+ let current_loop = self . current_loop ( ) ;
1394+
1395+ if let Some ( did_not_hit_break_var) = current_loop. did_not_hit_break_var {
1396+ // `did_not_hit_break_var = false` means we hit a break
1397+ let zero = self . builder . numeric_constant ( false , NumericType :: bool ( ) ) ;
1398+ self . builder . insert_store ( did_not_hit_break_var, zero) ;
1399+ }
1400+
1401+ let loop_end = current_loop. loop_end ;
12451402 self . builder . terminate_with_jmp ( loop_end, Vec :: new ( ) ) ;
12461403
12471404 Err ( RuntimeError :: BreakOrContinue { call_stack : CallStack :: default ( ) } )
0 commit comments