Skip to content

refactor(feature): bring the NEON float ADM kernels to the lint and HISS standard (ADR-1142) - #1865

Merged
lusoris merged 1 commit into
masterfrom
refactor/float-adm-neon-standards
Oct 2, 2026
Merged

lusoris merged 1 commit into
masterfrom
refactor/float-adm-neon-standards

Conversation

@lusoris

@lusoris lusoris commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fourth and last PR of standards batch B1 (the ADM family, plan in #1856): the two NEON float ADM kernel files are at the lint and HISS standard, and the three remaining batch files that had no findings left get their SPDX line or lose a dead suppression. No bit of any score moves.

arm64 cpu cuda hip sycl
core/src/feature/arm64/float_adm_dwt2_neon.c, clang-tidy 6 → 0 not compiled not compiled not compiled not compiled
core/src/feature/arm64/float_adm_neon.c, clang-tidy 5 → 0 not compiled not compiled not compiled not compiled
core/src/feature/adm_tools.c 0 → 0 0 → 0 0 → 0 0 → 0 0 → 0
core/src/feature/x86/adm_avx2.c, adm_avx512.c not compiled 0 → 0 0 → 0 0 → 0 0 → 0
lane total in scripts/ci/tidy-baseline-<lane>.json 569 → 558 unchanged unchanged unchanged unchanged

HISS: the row of float_adm_dwt2_neon.c is gone (136 lines): .standards-baseline.json 247 → 246, recorded with praetorctl baseline --record; the README count follows. Suppressions left in the five files: the two cited readability-non-const-parameter ones in each of adm_avx2.c / adm_avx512.c, untouched.

Base: master 25fe95d73, which has #1856 and #1859. This PR and #1864 both change .standards-baseline.json and the README count; the second to land needs the baseline re-recorded (praetorctl baseline --record; 244 with both). Their tidy baselines do not overlap (#1864: cpu, cuda, hip, sycl; this one: arm64).

What changed

The findings, by check (arm64 lane):

Check float_adm_dwt2_neon.c float_adm_neon.c Fixed by
bugprone-implicit-widening-of-multiplication-result 4 2 row pointers formed with (ptrdiff_t)i * stride
readability-isolate-declaration 1 3 int i, j; became loop-scoped declarations
readability-function-size 1 0 the split below

float_adm_dwt2_neon() is the wavelet adm.c dispatches on aarch64. It keeps its name and signature and loops over the output rows:

Helper Holds
dwt2_vertical_4_neon() the four acc = vaddq_f32(acc, vmulq_laneq_f32(sN, f, N)) steps from +0 for four columns; called with the low-pass taps, then with the high-pass taps
dwt2_vertical_row_neon() the vertical pass of one row: the 4-wide loop, then the scalar tail
dwt2_horizontal_row_neon() the scalar horizontal pass of one row

This is the shape of the scalar reference (adm_dwt2_s() over adm_dwt2_vert_pass_s() and adm_dwt2_horiz_pass_s() in adm_tools.c). Every sum still starts at +0 (signed-zero parity with the scalar code) and adds one product per statement in the same order, with the same float temporaries; no helper boundary cuts an expression. Each function carries the GCC optimize("-ffp-contract=off") attribute, as the scalar three do: GCC's arm_neon.h implements vaddq_f32 and vmulq_laneq_f32 as plain + and *, so the guard matters for a build outside the meson carve-out. The compiled object has one symbol (all helpers are inlined) and no fused multiply-add instruction.

The vector sum sits in its own helper because clang's arm_neon.h implements the intrinsics as statement expressions: with the sum inline, the vertical helper counted 129 statements against the limit of 120.

The other three files:

  • adm_tools.c: // NOLINTNEXTLINE(readability-function-size) on adm_dwt2_s(), 28 lines long, suppressed nothing and is removed; the comment above the wavelet said it "stays a single function on purpose" while it is three functions, and now describes them. No code changes.
  • x86/adm_avx2.c, x86/adm_avx512.c: SPDX line (BSD-2-Clause-Patent; Netflix files, upstream has both). No other change.
  • The two NEON files get the SPDX line too (BSD-2-Clause-Patent, the licence their headers state).

Not one bit moves

  • Scores on aarch64 (GCC, under qemu), against the record taken from master 513d2a6fc before the batch started (this PR's base 25fe95d73 reproduces it, x86 and aarch64): every adm and float_adm output with debug=true under 21 option sets and the model scores at --precision max, Netflix 576x324 at 8, 10, 12 and 16 bit, both 1080p checkerboards, BBB 3840x2160, 14 small sizes from 17x17, for scalar and NEON dispatch: 1066 of 1066 cases identical (127,704 values). The NEON cases run the refactored wavelet.
  • Object code. aarch64 (138 objects): the two kernels' objects differ from the base build and nothing else. x86 (155 objects): none differs, so the x86 scores cannot move (and the x86 record, 1695 cases, is identical).
  • Unit tests under qemu. test_float_adm_dwt2_neon (bit-exact against adm_dwt2_s(), geometry sweep, signed zero) and test_float_adm_neon (bit-exact against the scalar references of the other three kernels): pass before and after. All 20 ADM test binaries of the aarch64 build pass. x86 --suite=fast: 243 of 243.
  • The kernels, old against new. The base versions (renamed with -D) and the new ones linked into one driver, every output byte compared: widths 2 to 200 at nine heights and five large frames, five input classes (picture data, signed fractions, arbitrary bit patterns with NaN and infinities, signed zeros, 16-bit data), all four functions. 35,920 comparisons, 0 differences. Six changes planted in the new code are each detected (8543, 5870, 8977, 5000, 117 and 7286 differences).
  • Netflix golden gate. x86-64 GCC: 271 passed, 12 skipped before and after. aarch64 GCC under qemu: 271 passed, 12 skipped before and after.
  • GPU twins. None includes these files, and adm_tools.c has no code change; nothing to re-run.
  • scripts/dev/preflight.sh --stage msvcism: pass.

Type

  • feat — new feature
  • fix — bug fix
  • perf — performance improvement
  • refactor — no behavior change
  • docs — documentation only
  • test — test-only
  • build / ci — tooling / infra
  • port — cherry-pick from upstream Netflix/vmaf
  • sycl / cuda / simd — backend-specific

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally. (clang-format and the commit hooks; clang-tidy 0 for every touched file on every lane that reads it.)
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build. (--suite=fast on a CPU build: 243 of 243; the aarch64 ADM tests under qemu: 20 of 20.)
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2. (NEON against scalar is bit-identical: the two unit tests and the 1066-case record.)
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below. (No arithmetic is touched.)
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header (see CONTRIBUTING.md). (None added; SPDX lines added to four files.)
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE: and the migration path is documented below. (Not breaking.)
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md. (No ADR: ADR-1142 is the rule.)

Bug-status hygiene (ADR-0165)

  • docs/state.md updated in this PR. no state delta: refactor to the lint and HISS standard; no open row names these files' debt.

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.
  • If I believe a golden value must change, I have explained why below AND pinged @lusoris for a CODEOWNERS exception. (None changes.)

Deep-dive deliverables (ADR-0108)

  • Research digest — no digest needed: the findings and the split are described here and in the rebase note.
  • Decision matrix — no alternatives: only-one-way fix. ADR-1142 is the rule; the split follows the scalar reference's own two passes.
  • AGENTS.md invariant note — core/src/feature/arm64/AGENTS.md, new bullet "float_adm_dwt2_neon() row helpers": the helper map, the attribute every helper needs, why the vector sum is its own helper.
  • Reproducer / smoke-test command — pasted below under "Reproducer".
  • CHANGELOG fragment — changelog.d/changed/float-adm-neon-lint-hiss-standard.md.
  • Rebase note — docs/rebase-notes.md, "The NEON float ADM wavelet kernel is split into row helpers", with the map from the former single function to the helpers.

Reproducer

meson setup build-arm64 core --cross-file build-aux/aarch64-linux-gnu.ini \
  -Denable_cuda=false -Denable_sycl=false -Db_lto=false
ninja -C build-arm64
python3 scripts/ci/write-compile-commands.py --build-dir build-arm64
python3 scripts/ci/tidy-ratchet.py --lane arm64 --build-dir build-arm64 \
  --extra-arg=--target=aarch64-linux-gnu --extra-arg=--sysroot=/usr/aarch64-linux-gnu \
  --only core/src/feature/arm64/float_adm_dwt2_neon.c \
  --only core/src/feature/arm64/float_adm_neon.c   # 0 warnings in both files
QEMU_LD_PREFIX=/usr/aarch64-linux-gnu build-arm64/test/test_float_adm_dwt2_neon   # 3 tests run, 3 passed
QEMU_LD_PREFIX=/usr/aarch64-linux-gnu build-arm64/test/test_float_adm_neon        # 3 tests run, 3 passed
praetorctl baseline --verify      # 246 recorded, 246 active
make test-netflix-golden-arm64    # 271 passed, 12 skipped

Known follow-ups

  • core/test/test_float_adm_dwt2_neon.c (4 findings) and core/test/test_float_adm_neon.c (7) on the arm64 lane belong to batch B11 (core/test) and are not touched here.
  • The header entries of adm_tools.h, adm_csf_tools.h, adm_options.h and integer_adm.h in the tidy baselines are still at their old counts (see refactor(feature): bring the ADM headers to the lint standard (ADR-1142) #1856): the ratchet's scoped write covers translation units only.

…ISS standard (ADR-1142) (#1865)

* refactor(feature): bring the NEON float ADM kernels to the lint and HISS standard (ADR-1142)

float_adm_dwt2_neon(), the float ADM wavelet that adm.c dispatches on
aarch64, was one function of 136 lines. It keeps its name and signature
and loops over the output rows, calling

- dwt2_vertical_row_neon(): the 4-tap vertical pass of one row, the
  4-wide loop over dwt2_vertical_4_neon() (once with the low-pass taps,
  once with the high-pass taps), then the scalar tail
- dwt2_horizontal_row_neon(): the scalar horizontal pass of one row

Every sum still starts at +0 and adds one product per statement, in the
same order, with the same float temporaries; no helper boundary cuts an
expression. Each function carries the GCC optimize("-ffp-contract=off")
attribute, as adm_dwt2_s() and its two helpers do. float_adm_neon.c:
one declaration per line and (ptrdiff_t)i * stride for the row
pointers. SPDX lines added to both files and to x86/adm_avx2.c and
x86/adm_avx512.c. adm_tools.c: a NOLINTNEXTLINE(readability-function-
size) on adm_dwt2_s() suppressed nothing and is removed, and the comment
that said the wavelet stays one function describes the three functions
it is.

clang-tidy, arm64 lane: 6 to 0 and 5 to 0 (6 bugprone-implicit-widening-
of-multiplication-result, 4 readability-isolate-declaration, 1
readability-function-size), baseline tightened by the ratchet's scoped
write. adm_tools.c, adm_avx2.c and adm_avx512.c stay at 0 on every lane
that reads them. HISS: one row removed, 247 to 246.

Not one bit moves. aarch64 GCC under qemu: 1066 of 1066 cases (127 704
values) of the adm / float_adm record identical for scalar and NEON
dispatch; the two kernels' objects are the only ones that differ from
the base build; test_float_adm_dwt2_neon and test_float_adm_neon
(bit-exact against the scalar references, signed zero included) pass;
old against new on 35 920 inputs: no difference. x86: no object differs
from the base build. Netflix golden gate: 271 passed, 12 skipped on x86
GCC and aarch64 GCC, before and after.

* docs: regenerate the indexes and the citation map after rebasing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:refactor Internal refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant