while
while is the condition-driven loop. The body runs as long as the boolean
condition evaluates to true, and the condition is re-checked before every
iteration.
func:failsafe = int32(tbb32:err) {
exit 1;
};
func:main = int32() {
int32:x = 3i32;
int32:n = 0i32;
while (x > 0i32) {
n += 1i32;
x -= 1i32;
}
exit n; // 3
};
Re-evaluation
The condition is a live expression, not a snapshot. Anything it reads is read again on each pass, so the loop reacts to mutations made in the body:
while (x > 0i32) {
x -= 1i32; // observed by the next `x > 0i32` check
}
Mutating a parameter in the condition
A scalar function parameter may be mutated directly inside the loop that tests it. The parameter behaves like any local: the condition sees each update.
func:countdown = int32(int32:x) {
while (x > 0i32) {
x -= 1i32;
}
pass x; // 0
};
Historical note (v0.38.8): earlier builds could spin forever here because a scalar parameter was bound to its raw SSA argument and the condition was generated once against the original value. Scalar
int/floatparameters are now promoted to stack storage in the prologue, so loop conditions observe in-body mutation. Regression coverage: bug908, bug909.
Empty and one-shot loops
while (false) { ... } never runs its body. A while whose condition is
already false on entry contributes nothing — useful when pairing with an
invariant, where the entry obligation must still hold for the zero-iteration
case (see Invariants and contracts).
When to prefer a counted form
If the condition is really "count from a to b", reach for
loop or till instead — the intent reads more clearly and
the counter is managed for you.
Next: loop.