Repository navigation
loops - #106
Merged
Merged
loops#106
Conversation
varun-r-mallya
commented
Sep 24, 2026
Member
varun-r-mallya
requested review from
r41k0u
and
a balanced review from Copilot
September 24, 2026 19:07
The annotation types the slot, so `x: c_int32 = 0` is an i32 where the literal alone would give an i64; the store goes through the same handle_variable_assignment plain assignment uses. A bare `x: T` makes the slot and binds nothing. The already-bound and global-shadowing bookkeeping that Assign allocation did inline moves into _bind_name, which both statements now call.
handle_if processed its branches without passing ret_type down, so a return inside an if fell back to process_stmt's i64 default: a -> c_int32 function got `ret i64 7`, which llc rejects. The branches now go through process_block, which threads ret_type and stops at a statement that ends the block instead of emitting after its terminator.
`for i in range(...)` steps a hidden induction counter, allocated in the entry block next to the loop variable, and copies it into the variable each iteration, so rebinding the variable in the body leaves the trip count alone as in Python (and the loop stays visibly bounded for the verifier). The counter is 64-bit, signed unless the bounds make C's arithmetic unsigned. Bounds are evaluated once, before the loop; the step must be a nonzero integer literal, because its sign picks the loop test. `while` re-evaluates its test at the top of each iteration. `break` and `continue` branch through a loop-target stack on the compilation context; outside a loop they are a SyntaxError, as in Python. A loop's else-branch runs on normal exit only. Anything but range() as the iterable is rejected with NotImplementedError. The allocation pass now descends into loop bodies and counts helper temps in loop headers. Checked against clang -O2 on tests/c-form/loops.bpf.c: the constant-bound cases fold to the same ret, and a helper-bounded loop gets the same rotated loop.
- passing_tests/loops/: the seven range/while/break/continue/nested tests move over from failing_tests/, joined by rebinding the loop variable, a negative step, loop else-branches, and a helper-bounded loop over a @bpfglobal that survives -O2 as a real loop for the verifier. - failing_tests/loops/: break outside a loop, and a non-literal range step. - for_map_items stays xfail: map iteration needs its own design. - c-form/loops.bpf.c: the clang reference the lowering was checked against.
The remove-tabs pre-commit hook rejects tab indentation outside docs/ and Makefiles, which failed the Format job.
- `pass` is accepted as a statement, the natural body of a loop run for its side effects. - Falling off the end of a function returns 0 in the function's own return type. After `while True` that block is unreachable, but a c_int32 function still emitted `ret i64 0`, which llc rejected. - A range() step other than 1 could wrap the counter past INT64_MAX or UINT64_MAX back inside the range and loop forever. The step block now ends the loop when the distance left to stop, exact as an unsigned number, is no longer than the step. That is a normal exit, so the else-branch runs. Steps of 1 cannot overshoot and keep the plain loop, so the helper-bounded loop the verifier walks is unchanged. - A step of 2**64 or more no longer truncates to 0: it is a compile error, since it does not fit the counter. Steps from 2**63 up are emitted by their bits. - The counter's signedness folds each bound into a signed 64-bit type rather than joining the bounds alone, so only a c_uint64 bound makes it unsigned: range(-1, n) over a c_uint32 n runs 4 times, as in C with an __s64 counter.
Levels 1 and 2 only prove a loop compiles; one can still run the wrong number of times, or forever. test_loops_run.py JIT-compiles each helper-free passing_tests/loops case on the host (framework/host_jit.py, in a subprocess with a timeout) and checks its return value against what CPython returns for the same body. - passing_tests/loops/: pass bodies, `while True` exited by a return in a c_int32 function, step overflow past INT64_MAX, INT64_MIN and UINT64_MAX, a c_uint32 stop with a negative start, and loop variables that must stay one local (a nested same-name loop, a read after the loop). - failing_tests/loops/range_step_too_wide.py: a 2**64 step is a compile error. - reuse_loop_var_signedness.py is xfail in test_loops_run.py: every local gets one slot typed by its first binding, so a second loop's `i` inherits the first loop's unsigned type. `x = n_u64; x = -3` does the same, so the fix is general, not loop-specific.
varun-r-mallya
force-pushed
the
feat/loops
branch
from
September 25, 2026 18:15
36f708e to
e9e067e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.