Repository navigation
jitv2: skip the FP exception model for ordinary operands - #328
Merged
Merged
Conversation
A guest `fadd.s` in a jitv2 build emitted about 106 x86 instructions around one `vaddss`. The cost was the model, not the arithmetic: two per-operand denormal/quiet-NaN checks, the by-value signalling-NaN flags, the result-denormal rule, and an FCSR update that stored Cause on every instruction. Classify the operands once instead. If both are ordinary finite numbers — biased exponent neither zero nor all ones — then no operand check can trap, no signalling NaN can be present, and (for DIV) the divisor cannot be zero, so the instruction provably raises no flag. That arm computes the result, applies the unchanged result-denormal rule, and clears Cause only when it is set: rewriting a Cause field that is already clear writes back the value already in memory, which the guest cannot observe, and skipping it also removes a store-to-load dependency through `core.fpu_fcsr` between consecutive FP instructions. Everything else — zero, denormal, infinity, NaN, and a negative square root — takes the previous body verbatim, out of line, so the exception behaviour is the same code as before. The interpreter is untouched. Measured with the isolated kernels from the study below (single-kernel images, R4400, `--cache shadow`, five interleaved runs per cell, median, spread under 2% except where noted): kernel before after gain fadd.s indep 8.16 4.25 1.92x fadd.s dep 7.25 4.84 1.50x fadd.d indep 8.21 4.08 2.01x fadd.d dep 8.16 4.27 1.91x fmul.s indep 8.18 4.41 1.86x fmul.s dep 7.73 4.83 1.60x fmul.d indep 8.16 3.99 2.04x fdiv.s indep 8.52 4.20 2.03x fdiv.s dep 8.45 7.22 1.17x fdiv.d indep 7.71 4.29 1.80x fsqrt.s indep 5.87 4.02 1.46x fsqrt.d indep 6.08 3.45 1.76x The pure-conversion kernels, which this change does not touch, stay put (cvt.s.d 4.04 -> 3.84, cvt.w.s 10.48 -> 11.08, cvt.l.d 11.29 -> 11.57), which is the control. Semantics: the jitv2 equivalence suite (JIT vs interpreter snapshots, FCSR included, plus a new test that seeds Cause and Flag and checks that ordinary operand arithmetic clears Cause and keeps the sticky flags), the FPU/FCSR cpu-tests under jitv2 (identical pass/fail set before and after: 2620 checks passed, 126 failed, same 29 test identities), and `cargo test --workspace`. Credit: Jupiter Belic and irix7/iris — "Guest memory and FPU hot paths: what is actually slow", https://github.com/irix7/iris/blob/510adc512dc59395ba3ba2a42426bda09b11b9e4/clanker/perf/mem-and-fpu-hot-paths.md ; the measurements, the ranked recommendation this implements and the isolated-kernel method are that fork's work.
Owner
|
nice one! |
belikh
added a commit
to irix7/iris
that referenced
this pull request
Oct 25, 2026
Adopt the upstream work that landed since the fork's last sync: - jitv2: skip the FP exception model for ordinary operands (techomancer#328) - jitv2: inline the FPU integer conversions (techomancer#332) - serial: wall-clock timestamps for --serial-log (techomancer#325) - physical: a sparse, host-mapped decode table over 40 bits - pit: implement the 8254 read-back command - O2: the AIC-7880 specification, shared [scsi.N] disks, fresh serial log - docs: Odyssey references and the AIC-7880 emulation notes Conflict resolutions: - serial log: take upstream's one shared sink and the off|wall timestamp modes; delete the fork's guest/both synthetic-clock sink and its partial-line flusher. The CI and TCP paths still share the sink, and the fork's per-consumer console cursors are unchanged. - config: use dev::serial_log::SerialLogTimestamps; remove the local enum, the microsecond formatter and the guest/both parsing tests. - CHANGELOG/FEATURES/HELP: keep the fork's October sections and the instance/fleet entries, adopt upstream's JIT v2 and O2 text, and drop the duplicated O2 sync and machine-start entries. Tests: cargo test --workspace --features lightning,jitv2.
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.
jitv2: skip the FP exception model for ordinary operands
A guest
fadd.sin a jitv2 build emits about 106 x86 instructions around onevaddss. The cost is the exception model, not the arithmetic: twoper-operand denormal/quiet-NaN checks, the by-value signalling-NaN flags, the
result-denormal rule, and an FCSR update that stores Cause on every
instruction.
This classifies the operands once and gives the common case a straight-line
path, keeping the existing model out of line for everything else. The
interpreter is untouched.
The shortcut, exactly
If both operands are ordinary finite numbers — biased exponent neither zero
(zero/subnormal) nor all ones (infinity/NaN) — then:
fpu_check_denorm_operandis false for both operands, so nothing can trapas Unimplemented Operation;
fpu_arith_flags_snan_onlyis zero;So the instruction provably raises no flag (this model raises no other
flag for ADD/SUB/MUL/DIV), and the fast arm only has to:
rule (an underflowing result is reachable from ordinary operands, so that
check stays on both arms);
Causemust reflect only the lastinstruction, and rewriting a Cause field that is already clear stores the
value that is already in memory, which the guest cannot observe. Skipping
that store also removes a store-to-load dependency through
core.fpu_fcsrbetween consecutive FP instructions.SQRT additionally requires a non-negative operand, since a negative nonzero
operand raises Invalid.
Everything else — zero, denormal, infinity, NaN, a negative square root, and
DIV by zero — takes the previous body verbatim, just in an out-of-line
cold block: the same two operand checks, the same by-value flags, the same
result-denormal rule and the same full FCSR update (
Cause, stickyFlag,Enable checks and
EXC_FPE).Static code check with the study's metric (
IRIS_JIT_DISASM=1, median x86instructions between consecutive
vaddssin the compiled region): 106before, 74 after (the study measured 109 on its own image).
Measurements
Host: Intel Core i5-8500T, 6 cores, Linux x86_64 (load 2.78–3.59 during the
runs; other long-running work was present on the box, so all cells are
interleaved within each repeat).
Build:
cargo build --release --features lightning,jitv2, commited17f88plus this change vs the same tree without it. Runs:
--cpu r4400 --cache shadow, five runs per cell, median with[min–max]; the interpretercolumn is a separate unchanged
lightningbuild,--cache full, three runs,shown for scale.
Kernels are the study's isolated kernels (eight independent accumulators =
throughput, one dependent accumulator = latency), rebuilt as one kernel per
image: the multi-kernel probe image was measured first and is dominated by
the host-page/code-placement sensitivity the study already documents (an
untouched
cvt.s.dkernel moved 3x between builds), so it cannot attribute achange to the FP model. One kernel per image removes that confound. ns per
guest FP operation:
fpu/add_s_thrfpu/add_s_latfpu/add_d_thrfpu/add_d_latfpu/mul_s_thrfpu/mul_s_latfpu/mul_d_thrfpu/div_s_thrfpu/div_s_latfpu/div_d_thrfpu/sqrt_s_thrfpu/sqrt_d_thrfpu/cvt_s_d(control)fpu/cvt_w_s(control)fpu/cvt_l_d(control)fpu/cmp_s(has add/sub)The conversion kernels are untouched by this change and stay within ±5%;
they are the control that the box was not drifting. Dependent DIV stays
limited by the host divider (as the study predicted: ~1.5x ceiling there),
and dependent SQRT by the host sqrt unit.
Reproduce (single-kernel image built from the study's
bench-microkernels;the images are scratch, not committed):
CARGO_TARGET_DIR=... cargo build --release --bin iris --bin iris-bench \ --features lightning,jitv2 iris-bench run --iris target/release/iris --elf <one-kernel>.elf \ --config <bare.toml> --label <cell> --out <dir> --timeout 300 -- \ --cpu r4400 --cache shadowWhat proves the semantics
cargo test -p iris --features lightning,jitv2— 1748 passed, 0 failed.This includes the whole jitv2 equivalence suite, which compares the JIT
against the interpreter on a full
CoreSnapshot(GPRs, FPRs, PC andfpu_fcsr) for every FP instruction shape. A new test,arithmetic_clears_preexisting_cause_and_keeps_sticky_flags, seeds FCSRwith
Cause.V|Cause.ZandFlag.V|Flag.Iset and checks, for ADD/SUB/MUL/DIVin S and D, that both engines clear Cause, keep the sticky flags and agree
exactly — i.e. the "Cause is already set, clear it" arm is covered, not just
the skip-the-store one.
cpu-tests(bare-metal MIPS FPU suite: arithmetic, denormals, traps,compare, FR0/FR1, breadth, vectors) run under the jitv2 build before and
after: identical result, 2620 checks passed, 126 failed, same 29 test
identities. The failures are the standing jitv2/interpreter FPU-model
findings, unchanged by this PR.
cargo test --workspace— all test binaries pass (1429 tests, 0 failed).Risk
The cold path is the previous code, unchanged, so any operand class for which
the fast-path proof does not hold still runs the full model. The only new
observable behaviour is which arm an instruction takes. The fast path's
arithmetic is the same IR op on the same operand bits as the cold path's, so
results (including overflow to infinity, which this model does not flag) are
identical.
The cost is code size: each FP arithmetic emitter now contains both arms
(measured +6.6% total compiled bytes on the multi-kernel probe image). Cold
blocks are marked cold so the hot path stays contiguous.
Credit
The measurement study, the ranked recommendation this implements and the
isolated-kernel method come from Jupiter Belic's
irix7/irisfork:clanker/perf/mem-and-fpu-hot-paths.mdat510adc5.