ADR-1469: the psnr_hvs SIMD butterfly is two functions, cut at the same statement in the AVX2 and the NEON twin¶
- Status: Accepted
- Date: 2026-10-02
- Deciders: lusoris
- Tags:
simd,avx2,neon,psnr-hvs,bit-exact,touched-file-rule,rc3,fork-local
Context¶
core/src/feature/x86/psnr_hvs_avx2.c and core/src/feature/arm64/psnr_hvs_neon.c each held the 8-point DCT butterfly of the scalar od_bin_fdct8() as one function, od_bin_fdct8_simd(), more than 80 lines long (82 in the NEON file). ADR-0159 kept it in one piece so that it reads line by line against the scalar source, and the function carried a readability-function-size suppression that cites ADR-0141 §2. The repository's HISS-04 limit is 60 lines per function; both files had the finding in the baseline.
Fixing the masking threshold of calc_psnrhvs_neon() (T-PSNR-HVS-NEON-NOT-SCALAR-BITS-2026-10-02) touches the NEON file. A touched file loses its baseline exemptions, and the standing rule is that the audit's -touched-debt-delta-reason is not used (ADR-1298, ADR-1289). So the function has to meet the limit, in a file whose kernels must keep returning the scalar's bits.
Decision¶
The butterfly is cut once. od_bin_fdct8_even_simd() holds the first 21 statements of the scalar (the +1/-1 butterflies and the even half, after which t0, t2, t4 and t6 are final) and od_bin_fdct8_odd_simd() the remaining 13 (the embedded 4-point type-IV DST). Every statement keeps the scalar's text, order and names; the working values travel in od_fdct8_state. od_bin_fdct8_simd() keeps its signature, applies the input permutation and calls the two. Both twins are cut at the same statement, and neither carries a function-size suppression any more. This replaces the "network must stay together" clause of ADR-0159 for these two files; the scalar source is not touched.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Keep one function and pass -touched-debt-delta-reason | Smallest diff | Suppresses the gate rather than satisfying it | Out of bounds (ADR-1298, ADR-1289) |
| Cut only the NEON twin, the file the fix touches | The x86 object stays byte-identical | The twins stop reading side by side, against the twin-update rule of core/src/feature/arm64/AGENTS.md; the AVX2 finding stays in the baseline | The two files move together |
Work on the fields directly (s->t1 = ...), no local copies | 17 lines shorter per stage | The statements are no longer the scalar's text | The side-by-side read is the reason the function existed in one piece |
| Reach 60 lines by deleting comments and blank lines | No structural change | Removes the constants' explanations, which mirror the scalar's | Trades the audit aid for a line count |
| One helper per embedded transform (four or five) | Shorter functions | More boundaries and more state passed than the limit needs | One cut is enough |
Consequences¶
- Positive: both files report no HISS-04 finding and no clang-tidy warning, without a suppression.
- Positive: values are unchanged.
test_psnr_hvs_avx2andtest_psnr_hvs_neoncompare the DCT with the scalar bit for bit,test_psnr_hvs_dispatch_invariancethe whole extractor path, and thepsnr_hvsreports of nine fixtures are identical before and after the cut on x86-64 and aarch64, with GCC and with clang. - Neutral: the machine code is equivalent, not always identical. aarch64 clang: identical apart from the line numbers of the
assert()calls. aarch64 GCC: three loads name base and index register in the other order. x86-64 clang: commuted operands ofvpaddd. x86-64 GCC: a larger stack frame for the DCT (0x180bytes,0x160before). No cost measured on x86-64:psnr_hvstakes 23.6 ms per 1920x1080 frame before and after with GCC, 29.0 and 28.4 ms with clang (user CPU time, minimum of seven interleaved runs of 48 frames on one core of a Ryzen 9 9950X3D). - Neutral: the LLVM IR snapshot of
od_bin_fdct8x8_avx2(make ir-diff, ADR-0918) is regenerated. Its drift is fourassert()line numbers, which had drifted on master before this change, and one integer add whose operands swap places; the IR ofcalc_psnrhvs_avx2is unchanged. - Negative: reading the network against the scalar now means reading two functions; the scalar stays one.
- Neutral / follow-ups: a change to
od_bin_fdct8()upstream is mirrored in both stages of both twins; the cut stays where the even outputs are final.
References¶
- ADR-0159, ADR-0160 — the bit-exact ports.
- ADR-0141, ADR-0278 — the touched-file rule and the suppression this removes.
- ADR-1289, ADR-1298 — the escape hatch is not used.
docs/state.md:T-PSNR-HVS-NEON-NOT-SCALAR-BITS-2026-10-02.- Source:
req(coordinator, 2026-10-02): "ADR-0160 is Accepted and frozen: a new ADR supersedes the false claim if the design changes; a pure bug fix cites it."