bench_bitwise is 20.4× slower than Node (56956 vs 2787, checksums identical after #7403 made it measure anything at all). The cause is one instruction choice.
Evidence chain
The hot loop contains no runtime helper calls at all — it is pure LLVM arithmetic, so the usual "native-able primitive became a runtime call" story does not apply here. The op mix per iteration:
4 frem ← the problem
3 fsub 3 fadd 2 fmul 2 sitofp 2 fcmp 1 icmp 1 add
frem is not an aarch64 instruction. It lowers to a libm call:
$ otool -tvV … | grep 'bl.*fmod'
4 × bl _fmod # in runArithmeticBenchmark
And the profile says it dominates:
Four fmod calls per iteration × 10M iterations. The source is ordinary integer arithmetic:
result = result + (a % 1000);
result = result - (b % 1000);
result = result + ((a * 3) % 10000);
result = result - ((b * 2) % 10000);
V8 keeps these as SMIs and uses integer modulo.
The mechanism already exists — and knows this is the problem
stmt/loops.rs's emit_object_array_write_index_i64 emits srem, and its doc comment names the exact issue:
"the counters' native i32 registers drive i64 add/sub/mul/srem directly: no float round-trip and, critically, no frem (which lowers to an fmod LIBRARY CALL on AArch64 — ~10ns per element). srem equals JS % on the proven domain (nonnegative dividend, positive divisor)."
So the correctness argument is already worked out and the emitter is already written.
Why it does not fire here
The integer path is reachable only through ObjectArrayWriteNumber, whose variants are OuterCounter | InnerCounter | Constant | Add | Sub | Mul | Mod — the #6812 object-array-write index pattern. a and b in bench_bitwise are loop-carried locals (a = a + 1, reset by a conditional), not loop counters, so they never enter that domain and fall to the f64 emitter's frem.
Every other BinaryOp::Mod site emits frem unconditionally: expr/mod.rs:2240, expr/binary.rs:650, codegen/typed_abi/lower.rs:56 and :433, lower_call/scalar_method.rs:174.
Suggested direction
Widen the integer-domain proof from loop counters to loop-carried locals with a provable integer range. The correctness side-condition is already stated (nonnegative dividend, positive divisor) and the emitter already exists; what is missing is the analysis that admits more values into the proven domain.
This is the same lever #7296 proved worth 9.9× on matmul — see the engine plan's sequencing item 2, where reducing what must be treated as an opaque f64 pulls size and speed together.
Caveat
Timings taken at host load ~4–25. The 20.4× ratio and the 1754/fmod profile share are far outside that noise; exact figures want a quiet machine.
Related: #7396 (array-store guard siting), #7403 (this benchmark measured nothing until today), #7294 (engine plan, Part 2).
bench_bitwiseis 20.4× slower than Node (56956 vs 2787, checksums identical after #7403 made it measure anything at all). The cause is one instruction choice.Evidence chain
The hot loop contains no runtime helper calls at all — it is pure LLVM arithmetic, so the usual "native-able primitive became a runtime call" story does not apply here. The op mix per iteration:
fremis not an aarch64 instruction. It lowers to a libm call:And the profile says it dominates:
Four
fmodcalls per iteration × 10M iterations. The source is ordinary integer arithmetic:V8 keeps these as SMIs and uses integer modulo.
The mechanism already exists — and knows this is the problem
stmt/loops.rs'semit_object_array_write_index_i64emitssrem, and its doc comment names the exact issue:So the correctness argument is already worked out and the emitter is already written.
Why it does not fire here
The integer path is reachable only through
ObjectArrayWriteNumber, whose variants areOuterCounter | InnerCounter | Constant | Add | Sub | Mul | Mod— the #6812 object-array-write index pattern.aandbinbench_bitwiseare loop-carried locals (a = a + 1, reset by a conditional), not loop counters, so they never enter that domain and fall to the f64 emitter'sfrem.Every other
BinaryOp::Modsite emitsfremunconditionally:expr/mod.rs:2240,expr/binary.rs:650,codegen/typed_abi/lower.rs:56and:433,lower_call/scalar_method.rs:174.Suggested direction
Widen the integer-domain proof from loop counters to loop-carried locals with a provable integer range. The correctness side-condition is already stated (nonnegative dividend, positive divisor) and the emitter already exists; what is missing is the analysis that admits more values into the proven domain.
This is the same lever #7296 proved worth 9.9× on
matmul— see the engine plan's sequencing item 2, where reducing what must be treated as an opaque f64 pulls size and speed together.Caveat
Timings taken at host load ~4–25. The 20.4× ratio and the 1754/
fmodprofile share are far outside that noise; exact figures want a quiet machine.Related: #7396 (array-store guard siting), #7403 (this benchmark measured nothing until today), #7294 (engine plan, Part 2).