@@ -11,8 +11,12 @@ impl ArcLowerer<'_> {
1111 /// Lower `for i in <range> do body` using direct start/end projection.
1212 ///
1313 /// Range layout: `{i64 start, i64 end, i64 step, i64 inclusive}`.
14- /// Inclusive ranges (`0..=5`) use `i < end + inclusive` so both exclusive
15- /// and inclusive work with a single `Lt` comparison.
14+ /// The loop condition uses sign-aware comparison to avoid overflow:
15+ /// - Ascending (step > 0): `i < end` (exclusive) or `i <= end` (inclusive)
16+ /// - Descending (step < 0): `i > end` (exclusive) or `i >= end` (inclusive)
17+ ///
18+ /// The old approach (`i < end + inclusive`) overflows for `0..=INT_MAX`
19+ /// because `INT_MAX + 1` wraps to `INT_MIN`.
1620 #[ expect(
1721 clippy:: too_many_lines,
1822 reason = "range loop lowering with guard/latch/mutable-var SSA merge is inherently sequential"
@@ -80,18 +84,70 @@ impl ArcLowerer<'_> {
8084
8185 let start = self . builder . emit_project ( Idx :: INT , iter_val, 0 , None ) ;
8286 let end = self . builder . emit_project ( Idx :: INT , iter_val, 1 , None ) ;
83- // Field 3 = inclusive flag (0 or 1). Adding it to end makes `i < end + inclusive`
84- // work for both exclusive (i < end) and inclusive (i < end + 1 ≡ i <= end).
87+ let step = self . builder . emit_project ( Idx :: INT , iter_val, 2 , None ) ;
8588 let inclusive = self . builder . emit_project ( Idx :: INT , iter_val, 3 , None ) ;
86- let adjusted_end = self . builder . emit_let (
87- Idx :: INT ,
89+
90+ // Pre-compute loop-invariant direction/inclusive flags.
91+ // These are defined in the entry block and dominate the header.
92+ let zero = self
93+ . builder
94+ . emit_let ( Idx :: INT , ArcValue :: Literal ( LitValue :: Int ( 0 ) ) , None ) ;
95+ let step_pos = self . builder . emit_let (
96+ Idx :: BOOL ,
8897 ArcValue :: PrimOp {
89- op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Add ) ,
90- args : vec ! [ end, inclusive] ,
98+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Gt ) ,
99+ args : vec ! [ step, zero] ,
100+ } ,
101+ None ,
102+ ) ;
103+ let step_neg = self . builder . emit_let (
104+ Idx :: BOOL ,
105+ ArcValue :: PrimOp {
106+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Lt ) ,
107+ args : vec ! [ step, zero] ,
108+ } ,
109+ None ,
110+ ) ;
111+ let is_incl = self . builder . emit_let (
112+ Idx :: BOOL ,
113+ ArcValue :: PrimOp {
114+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Gt ) ,
115+ args : vec ! [ inclusive, zero] ,
91116 } ,
92117 None ,
93118 ) ;
94119
120+ // Zero-step guard: panic at runtime if step == 0.
121+ // Prevents infinite loop for ranges like `0..=0 by 0`.
122+ let step_is_zero = self . builder . emit_let (
123+ Idx :: BOOL ,
124+ ArcValue :: PrimOp {
125+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Eq ) ,
126+ args : vec ! [ step, zero] ,
127+ } ,
128+ None ,
129+ ) ;
130+ let panic_block = self . builder . new_block ( ) ;
131+ let loop_entry_block = self . builder . new_block ( ) ;
132+ self . builder
133+ . terminate_branch ( step_is_zero, panic_block, loop_entry_block) ;
134+
135+ // Panic block: emit "range step cannot be zero" and halt.
136+ self . builder . position_at ( panic_block) ;
137+ let panic_msg = self . interner . intern ( "range step cannot be zero" ) ;
138+ let msg_var = self . builder . emit_let (
139+ Idx :: STR ,
140+ ArcValue :: Literal ( LitValue :: String ( panic_msg) ) ,
141+ None ,
142+ ) ;
143+ let panic_fn = self . interner . intern ( "ori_panic" ) ;
144+ self . builder
145+ . emit_apply ( Idx :: UNIT , panic_fn, vec ! [ msg_var] , None ) ;
146+ self . builder . terminate_unreachable ( ) ;
147+
148+ // Continue in loop entry block.
149+ self . builder . position_at ( loop_entry_block) ;
150+
95151 // Entry jump args match header param order: [start, mut0, mut1, ...]
96152 let mut entry_args = vec ! [ start] ;
97153 entry_args. extend ( header_mut_params. iter ( ) . map ( |( _, pre_var, _) | * pre_var) ) ;
@@ -104,11 +160,76 @@ impl ArcLowerer<'_> {
104160 self . scope . bind_mutable ( name, param_var) ;
105161 }
106162
107- let in_bounds = self . builder . emit_let (
163+ // Sign-aware loop condition using boolean arithmetic:
164+ // asc_part = (step > 0) && (i < end)
165+ // desc_part = (step < 0) && (i > end)
166+ // base = asc_part || desc_part
167+ // incl_part = inclusive && (i == end)
168+ // in_bounds = base || incl_part
169+ //
170+ // For compile-time constant step/inclusive, LLVM constant-folds
171+ // the dead terms away, producing optimal `icmp slt`/`icmp sle`.
172+ let lt_val = self . builder . emit_let (
108173 Idx :: BOOL ,
109174 ArcValue :: PrimOp {
110175 op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Lt ) ,
111- args : vec ! [ i_var, adjusted_end] ,
176+ args : vec ! [ i_var, end] ,
177+ } ,
178+ None ,
179+ ) ;
180+ let gt_val = self . builder . emit_let (
181+ Idx :: BOOL ,
182+ ArcValue :: PrimOp {
183+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Gt ) ,
184+ args : vec ! [ i_var, end] ,
185+ } ,
186+ None ,
187+ ) ;
188+ let eq_val = self . builder . emit_let (
189+ Idx :: BOOL ,
190+ ArcValue :: PrimOp {
191+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Eq ) ,
192+ args : vec ! [ i_var, end] ,
193+ } ,
194+ None ,
195+ ) ;
196+ let asc_part = self . builder . emit_let (
197+ Idx :: BOOL ,
198+ ArcValue :: PrimOp {
199+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: And ) ,
200+ args : vec ! [ step_pos, lt_val] ,
201+ } ,
202+ None ,
203+ ) ;
204+ let desc_part = self . builder . emit_let (
205+ Idx :: BOOL ,
206+ ArcValue :: PrimOp {
207+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: And ) ,
208+ args : vec ! [ step_neg, gt_val] ,
209+ } ,
210+ None ,
211+ ) ;
212+ let base = self . builder . emit_let (
213+ Idx :: BOOL ,
214+ ArcValue :: PrimOp {
215+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Or ) ,
216+ args : vec ! [ asc_part, desc_part] ,
217+ } ,
218+ None ,
219+ ) ;
220+ let incl_part = self . builder . emit_let (
221+ Idx :: BOOL ,
222+ ArcValue :: PrimOp {
223+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: And ) ,
224+ args : vec ! [ is_incl, eq_val] ,
225+ } ,
226+ None ,
227+ ) ;
228+ let in_bounds = self . builder . emit_let (
229+ Idx :: BOOL ,
230+ ArcValue :: PrimOp {
231+ op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Or ) ,
232+ args : vec ! [ base, incl_part] ,
112233 } ,
113234 None ,
114235 ) ;
@@ -160,14 +281,11 @@ impl ArcLowerer<'_> {
160281 self . loop_ctx = prev_loop;
161282
162283 self . builder . position_at ( latch_block) ;
163- let one = self
164- . builder
165- . emit_let ( Idx :: INT , ArcValue :: Literal ( LitValue :: Int ( 1 ) ) , None ) ;
166284 let next = self . builder . emit_let (
167285 Idx :: INT ,
168286 ArcValue :: PrimOp {
169287 op : PrimOp :: Binary ( ori_ir:: BinaryOp :: Add ) ,
170- args : vec ! [ i_var, one ] ,
288+ args : vec ! [ i_var, step ] ,
171289 } ,
172290 None ,
173291 ) ;
0 commit comments