ifs Beside It That Cannot TellStatus: Accepted
Date: October 22, 2026
Answers: whether the compiler may write a statement of a block in another
place than the one it is written in, and when (issue
#781)
Related: Part I 8.1.1 (statement order is the written order),
ADR-306 D18, D19 (a proved check), ADR-376
(unsafe { assume(…) }), ADR-378 (the emitted code’s unsafe),
ADR-377 (the 1BRC programs)
A loop that adds a row to a slot reads naturally as
if v < slot.min { slot.min = v }
if v > slot.max { slot.max = v }
slot.sum += v
slot.count += 1
LLVM turns slot.sum += v into one instruction that adds into memory only
where the load, the add and the store stand in one basic block. The two ifs
end that block, so the load is taken before them and the store after: six
instructions where two do, in every turn. The same four lines with the two
writes first cost four instructions a row less, in Rust as in Nikaia; the
Rust that rustc gets from Nikaia, with only those lines swapped, counts the
same difference. LLVM’s own passes do not move the writes across the
branches, even where the overflow check is already gone; and where it is not,
they could not, not knowing that nothing between can stop the program.
The compiler knows it. It knows which places a statement reads and writes,
that slot.sum and slot.min are different fields, and which checks are
proved. A programmer should not have to know how a code generator forms its
blocks to write the natural order.
Other compilers: C and Rust compilers move memory writes freely under the as-if rule where nothing can observe the difference; GCC’s and LLVM’s store motion stops at anything that may trap or call out. Java’s JIT does the same with its own knowledge of which operations can throw.
ifs in front of it, where no program can tell.In a block, a statement S that assigns to a place (p = e, p op= e)
is emitted before an if T that stands directly in front of it, or in
front of it with only such ifs between, when all of these hold:
T neither reads nor writes p: every place T names differs from p in
a field, or is a local other than p’s root;S reads nothing T writes;T and S touch only memory;T nor S can stop the program: every check in them (an index,
an overflow, a division) is proved, and neither holds a panic, an
assert or a call that can throw;T does not leave: its arms hold only assignments and such ifs, no
break, continue, return or throw, so S runs after it on every
path, as it does before it.The block’s last statement, its value, is not moved.
Nothing else is moved, and nothing moves past a statement that is not such
an if.
Why this is the written order still (Part I 8.1.1). A program sees a
statement’s effects through what it reads later, through another task, or
through what it leaves when it stops. Condition 1 and 2 keep every read
seeing the value it saw. Condition 3 keeps another task from seeing anything
between the two: it meets this one only at a pause, a lock or a spawn, and
none moves. Condition 5 keeps S running exactly when it ran. Condition 4
keeps a stop from falling between them: were one side
able to stop, a drop in the teardown or the panic hook could see the other
side’s write done or not done, and where both could, which stops first would
change.
The order is chosen from the program and from what is proved, not from the
build’s profile or from remove-*: a debug build runs the statements in the
order the release build runs them, so a fault that the order exposes is found
in the debugger. Each statement keeps its own source line; a debugger steps
from the moved write to the if before it, as in any optimised code.
The emitted order is tested for a case that moves and for each condition of
D1 that keeps a case from moving: the if reads p; it writes what e
reads; one side calls an impure function; one side has a check that is not
proved; the if leaves.
min and max without a branch, so all four writes stand in
one block: a select costs a load, a compare, a move and a store per field
where the branch costs a compare against memory and a jump that is almost
never taken; four instructions a row more than today.benches/brc/fast-unsafe.nika keeps the natural order and gains what the
hand-swapped order gained.assume) lets more
move.