Parent JIT: #52 Arithmetic target: #46
Problem
After block accounting, register-resident numeric locals, compare/branch fusion, and hot-successor batching, the arithmetic kernel remained roughly 2.8x behind the then-accepted JSC direct result. Repeated A/B experiments showed that removing FP moves or constant-divisor setup was neutral: the critical path was keeping integer loop state as f64, validating it, converting it to an integer, dividing, and converting it back on every %.
Resolution
The guarded integer specialization landed as a sequence of general baseline-JIT changes:
69dfc786— unsigned entry guards before native work6011cc1b— integer provenance dataflow1fc7e9c1— bounded integer ranges5b8aacb3andb21cdfb7— branch refinement and conservative threshold widening8dd91738— checked unsigned 64-bit arithmetic61b379cc— integer register selection6a1c844c— guarded integer-region lowering
The implementation now:
- evaluates parameter kind/range guards before native step accounting or frame-slot mutation;
- propagates integer provenance and conservative ranges through loops, branches, and merges;
- keeps selected locals and operand values in GPRs and materializes canonical Number words at checkpoints/returns;
- lowers guarded integer add/sub/mul/comparisons and remainder without repeated f64 round trips;
- rejects or falls back before observable native work when entry assumptions fail, with checked range/overflow behavior rather than wrapping;
- retains exact bytecode step accounting, the 1,024-step checkpoint contract, termination/GC/GIL servicing, and interpreter fallback for fractional, NaN, negative-zero, escaped-frame, or unsupported shapes;
- selects only from bytecode/runtime guards—never source strings, function names, benchmark names, or call-site identity.
Focused tests cover guard acceptance/rejection, mixed-kind provenance rejection, bounded ranges, guarded remainder execution, register encodings, exact native step limits, and non-number fallback. The new zig build test-jit small root keeps those tests directly runnable without the monolithic integration artifact.
Accepted performance evidence
The July 15, 2026 128-byte-slab comparison report measures identical arithmetic source/work across engines:
- direct: 91.807 ms zig-js vs 356.516 ms JSC (3.88x zig-js throughput)
- all directly comparable direct, warmed-independent, and cold-lifecycle rows pass the JSC performance contract
- report: https://github.com/zig-utils/zig-js/blob/main/docs/.data/benchmark-comparison-2026-07-15-128-byte-slab.md
- raw samples: https://github.com/zig-utils/zig-js/blob/main/docs/.data/benchmark-comparison-2026-07-15-128-byte-slab.tsv
Required work
- Add per-parameter integer/range guards evaluated before native step accounting or slot mutation.
- Extend numeric dataflow with integer provenance and conservative ranges across loops/merges.
- Allocate integer locals/operand values to GPRs while retaining canonical Number publication at safepoints and returns.
- Lower integer add/sub/mul/comparisons and constant remainder without repeated f64 round trips.
- Define checked overflow handling through proofs, guarded fallback, or rejection before observable work.
- Preserve max-step errors, 1,024-step GIL/GC/termination checkpoints, NaN/-0/fractional fallback, and escaped-frame rejection.
- Add focused guard/deopt/range tests plus accepted order-balanced arithmetic evidence.
- Publish the symmetric JSC report only after the optimization batch and gates were material.