Skip to content

refactor(feature): bring adm.c to the lint and HISS standard (ADR-1142) - #1859

Merged
lusoris merged 5 commits into
masterfrom
refactor/adm-c-standards
Oct 2, 2026
Merged

lusoris merged 5 commits into
masterfrom
refactor/adm-c-standards

Conversation

@lusoris

@lusoris lusoris commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Second PR of standards batch B1 (the ADM family, plan in #1856): core/src/feature/adm.c is at the lint and HISS standard, and no bit of any score moves.

cpu cuda hip sycl arm64
core/src/feature/adm.c, clang-tidy 31 → 0 31 → 0 31 → 0 31 → 0 31 → 0
core/src/feature/float_adm.c, baseline entry (the file already measured 0) 0 0 5 → 0 5 → 0 4 → 0
lane total in scripts/ci/tidy-baseline-<lane>.json 376 → 345 734 → 703 752 → 716 837 → 801 606 → 571

HISS: the nine rows of adm.c are gone (compute_adm 260 lines, and eight goto): .standards-baseline.json 260 → 251, recorded with praetorctl baseline --record; the README count follows. Uncited NOLINTs: 0.

What changed in adm.c

compute_adm() keeps its name and its signature (adm.h, float_adm.c and the Cython harness see no difference) and is a 59-line function over helpers:

Helper Holds
adm_alloc_bands(), adm_alloc_indices(), adm_frame_alloc(), adm_frame_free() the band planes and the two index tables, allocated in upstream's order with upstream's stdout messages; every goto fail became a return, and the three frees happen once in compute_adm()
adm_scale_dwt2() the wavelet of one scale for both pictures (adm_dwt2_lo when the scale is skipped)
adm_scale_sums() adm_decouple, adm_csf_den_scale, adm_csf, adm_cm, adm_csf, adm_cm, in upstream's order with upstream's arguments (the options travel in one struct)
adm_accumulate_scales() the loop over the four scales and the four double accumulators

compute_adm() holds no kernel arithmetic, only call order and accumulation. Each statement that computes moved whole, with the type of every temporary: float per-scale sums added into double frame sums, aim_den before aim_num, w and h halved after the wavelet, numden_limit as written.

The findings, by check:

Check Count Fixed by
bugprone-casting-through-void 11 direct casts in init_dwt_band*(); the planes are carved out of one MAX_ALIGN buffer in MAX_ALIGN steps
modernize-use-nullptr 6 cited NOLINTBEGIN / NOLINTEND (ADR-1138: a C translation unit keeps NULL)
cert-err33-c 4 (void) on the printf / fflush of the allocation messages, as ms_ssim.c does
readability-isolate-declaration 3 one declaration per line
bugprone-assignment-in-if-condition 3 assignment before the test
bugprone-implicit-widening-of-multiplication-result 2 (size_t)row_bytes * 4
misc-use-internal-linkage 1 adm.c includes adm.h
readability-function-size 1 the split

One suppression in the file, the ADR-1138 block.

Removed: the two #ifdef ADM_OPT_DEBUG_DUMP blocks. They call write_image() and PRINTF(), which nothing in the tree defines, so they could not compile; the macro stays commented out in adm_options.h.

core/src/feature/AGENTS.md listed compute_adm among the functions that stay unsplit. That note is updated: the function has no arithmetic to split, and the rule it stood for (a helper boundary never cuts an expression) is kept.

Not one bit moves

  • Scores. Every adm and float_adm output with debug=true under 21 option sets and the scores of the default model and of vmaf_v0.6.1, at --precision max, against the record taken from master 513d2a6fc:

    Build Fixtures Dispatch Cases identical Values
    x86-64 GCC Netflix 576x324 at 8, 10, 12, 16 bit; both 1080p checkerboards; BBB 3840x2160 50 frames; 14 small sizes from 17x17 to 129x65 at 8 and 10 bit scalar, AVX2, AVX-512 1695 of 1695 271 962
    aarch64 GCC under qemu the same, fewer frames of the large clips scalar, NEON 1066 of 1066 127 704

    The option sets reach every argument compute_adm() passes on: adm_enhn_gain_limit, adm_csf_mode 1, 2, 5 and 9, adm_p_norm 2.5 and 4, adm_bypass_cm, viewing distance and display height, noise weight and both CSF scales, adm_adm3_apply_hm / adm_dlm_weight / adm_min_val, four band weights, adm_skip_aim_scale and adm_skip_scale0.

  • Object code. One object file differs from the base build in each of the x86 (155 objects) and aarch64 (138) builds: feature_adm.c.o.

  • Netflix golden gate. x86-64 GCC: 271 passed, 12 skipped before and after. aarch64 GCC under qemu: 271 passed, 12 skipped before and after.

  • Unit tests. --suite=fast on the CPU build: 243 of 243.

  • Cython harness. compat/python-vmaf/core/adm_dwt2_cy.pyx text-includes adm.c for init_dwt_band_d(); that function keeps its signature and the three files it includes still compile together.

  • GPU twins. None includes adm.c or float_adm.c; nothing to re-run. (refactor(feature): bring the ADM headers to the lint standard (ADR-1142) #1856 covers the shared headers.)

  • 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 the file on five lanes.)
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build. (--suite=fast on a CPU build: 243 of 243.)
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2. (No SIMD or GPU file is touched; the dispatch paths were compared, see above.)
  • 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; the SPDX line of adm.c added.)
  • 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 row names this file's 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 function's own stages.
  • AGENTS.md invariant note — core/src/feature/AGENTS.md, "Split helpers must not split an expression": compute_adm leaves the unsplit list, with its helper map and the orders that must not change.
  • Reproducer / smoke-test command — pasted below under "Reproducer".
  • CHANGELOG fragment — changelog.d/changed/adm-c-lint-hiss-standard.md.
  • Rebase note — docs/rebase-notes.md, "compute_adm() is split into helpers; its debug-dump blocks are gone", with the map from upstream's statements to the helpers.

Reproducer

meson setup build-cpu core -Denable_cuda=false -Denable_sycl=false -Db_lto=false
ninja -C build-cpu
python3 scripts/ci/write-compile-commands.py --build-dir build-cpu
python3 scripts/ci/tidy-ratchet.py --lane cpu --build-dir build-cpu \
  --only core/src/feature/adm.c --only core/src/feature/float_adm.c   # 0 warnings
praetorctl baseline --verify      # 251 recorded, 251 active
build-cpu/tools/vmaf -r python/test/resource/yuv/src01_hrc00_576x324.yuv \
  -d python/test/resource/yuv/src01_hrc01_576x324.yuv -w 576 -h 324 -p 420 -b 8 \
  --no_prediction --feature float_adm=debug=true --precision max --json -o /dev/stdout -q
make test-netflix-golden          # 271 passed, 12 skipped
make test-netflix-golden-arm64    # 271 passed, 12 skipped

Known follow-ups

  • PRs 3 and 4 of the batch: the x86 and NEON float ADM kernels.

…ard (ADR-1142) (#1853)

* refactor(hip): bring the HIP runtime and host stubs to the lint standard (ADR-1142)

Standards batch B5, HIP side: the clang-tidy debt the hip lane records
for core/src/hip/ and core/src/feature/hip/, and the two findings of
core/src/hip/stubs.c that every other lane records.

- hip/kernel_template.c, hip/common.c: the stream and event handles are
  kept as uintptr_t (ADR-0241) and were converted back with
  integer-to-pointer casts (performance-no-int-to-ptr, 14 sites) and one
  const on a pointer typedef. They go through hip_handle.h now, the
  union the feature extractors already use. Same calls, same order,
  same streams and events.
- hip/stubs.c: the ADR-1138 NOLINT bracket for NULL in a C translation
  unit (modernize-use-nullptr, 2). C keeps NULL: MSVC's C mode has no
  nullptr.
- feature/hip/hip_hsaco_stubs.c: the stub macro's argument is a
  parenthesised declarator (bugprone-macro-parentheses).
- feature/hip/speed_chroma_hip.c, speed_temporal_hip.c: braces.
- feature/hip/float_adm_hip.c: a dead store in the build without hipcc,
  which the hip lane measures and its baseline did not record.

Baselines, each by the ratchet's scoped write: hip 752 to 733, cpu 376
to 374, cuda 734 to 732, sycl 837 (stubs.c 2 to 0), arm64 606 to 604.
No HISS row names these files.

Behaviour: every HIP twin was swept before and after on a gfx1036 at
--precision max (fourteen typical and stress fixtures and six small
ones, every gate feature, float_adm with debug): 427 runs, 17 800 of
17 800 values identical to the run before. Suites unchanged: HIP build
fast 256, gpu 72 and 1 skipped; CPU build fast 243;
test_gpu_dispatch_runtime passes.
…int standard (ADR-1142) (#1854)

* refactor(hip): bring the cambi and psnr_hvs device host code to the lint standard (ADR-1142)

Standards batch B5, HIP side, part 2. The hip clang-tidy lane is
configured without hipcc, so it analyses the -ENOSYS stubs of the HIP
host files and not the bodies under HAVE_HIPCC that a device runs. A
hipcc build measured 35 findings there, in two files, and the lane
itself shows 11 in a device header that its baseline does not record.

- integer_cambi_hip.c (24): the device arena was bound with sixteen
  casts through void * (bugprone-casting-through-void); one accessor,
  cambi_hip_arena_at(), returns the block and the assignment converts
  it. Kernel arguments are converted explicitly, a const leaves a
  pointer typedef, six statements get braces.
- integer_psnr_hvs_hip.c (11): the kernel argument arrays convert their
  pointer-to-pointer elements explicitly.
- integer_cambi/cambi_hip_device.h (11): the row and column offsets
  added to a pointer are widened before the multiplication
  (bugprone-implicit-widening-of-multiplication-result). Same offsets
  for every plane below 2^32 samples; the header's other indices are
  32-bit as before.

No baseline changes: the lane cannot see the first two, and the header
was at 0 in the baseline. T-TIDY-RATCHET-GPU-LANES-UNREPRODUCIBLE-2026-09-22
records the blind spot.

Behaviour: the sweep of every HIP twin recorded on the base was
repeated on a gfx1036 at --precision max: 427 runs, 17 800 of 17 800
values identical. cambi takes 14.1 ms per 1920x1080 frame (15.0 before)
and 84.6 ms per 3840x2160 frame (90.0 before); psnr_hvs is unchanged
(51.3 and 51.4 ms at 3840x2160). HIP build: fast 256, gpu 72 and 1
skipped, as before.
…42) (#1856)

* refactor(feature): bring the ADM headers to the lint standard (ADR-1142)

adm_tools.h, adm_csf_tools.h, adm_options.h and integer_adm.h report no
clang-tidy finding on the cpu, cuda, hip, sycl and arm64 lanes (145,
145, 145, 156 and 145 before).

adm_tools.h had 142: 141 bugprone-macro-parentheses, all in upstream's
nine ADM_CM_THRESH_S_* macros, and one portability-avoid-pragma-once.
Nothing has expanded those macros since ADR-1141 moved the masking
threshold into adm_tools.c::adm_cm_thresh3x3_s(), so they are removed
rather than parenthesised: a kept macro is dead text that would absorb
an upstream change without changing a score. The pragma goes; the
include guard upstream also has stays.

adm_csf_tools.h and adm_options.h lose the pragma. _USE_MATH_DEFINES is
the name MSVC's and MinGW's <math.h> look for and keeps a cited
suppression (ADR-1234). integer_adm.h is a C header a SYCL translation
unit includes, so the C++-only checks it trips there (modernize-use-
using, performance-enum-size, modernize-redundant-void-arg,
modernize-use-designated-initializers) are suppressed in one cited block
(ADR-1138), as its sibling headers do; one local becomes const.

Not one bit moves. Every object file of an x86 GCC release build (155)
and of an aarch64 GCC release build (138) is byte-identical before and
after, as are the HIP host objects and the SYCL objects that include
these headers. Every adm and float_adm output with debug=true and 21
option sets, and the model scores, at --precision max: 1695 of 1695
cases (271 962 values) identical on x86 for scalar, AVX2 and AVX-512,
and 1066 of 1066 (127 704 values) on aarch64 under qemu for scalar and
NEON. Netflix golden gate: 271 passed, 12 skipped on x86 GCC and on
aarch64 GCC, before and after. The CUDA, HIP and SYCL twins pass their
ADM tests and their adm and float_adm gate cells report 0.

The header entries of the five tidy baselines are not tightened here:
the ratchet's scoped write covers translation units only, and a full
write on this host would record seven misc-static-assert false positives
in core/tools/vmaf.cpp (T-TIDY-GLIBC-244-STATIC-ASSERT-FALSE-POSITIVE-
2026-10-02).
…ndard (ADR-1142) (#1858)

* refactor(cuda): bring the CUDA runtime and host files to the lint standard (ADR-1142)

Standards batch B5, CUDA side: every file under core/src/cuda/ and
core/src/feature/cuda/ measures 0 in the cuda clang-tidy lane.

- cuda/picture_cuda.c (5 recorded, 4 measured): the two CUDA_MEMCPY2D
  descriptors are initialised with their memory types as designators
  instead of {0}, which is no CUmemorytype enumerator
  (bugprone-invalid-enum-default-initialization); two braces.
- cuda/picture_cuda.h, cuda/cuda_helper.cuh: include guards that are
  not reserved identifiers.
- cuda/picture_cuda.c, picture_cuda.h, common.h, cuda_helper.cuh: the
  SPDX line of the licence their header states (BSD-2-Clause-Patent,
  Netflix-inherited, ADR-1250).
- cuda/common.h (3), cuda/cuda_helper.cuh (1): clang-tidy reads these C
  headers under a C++ translation unit and proposes `using` and an
  anonymous namespace; C has neither. Cited NOLINT brackets (ADR-0141).
- feature/cuda/integer_cambi_cuda.c (1): the extractor symbol is
  referenced by the registry; the tree's NOLINT for that (ADR-0278).
- feature/cuda/integer_psnr_hvs_cuda.c (14, none recorded): the module
  load moves into psnr_hvs_load_module() (readability-function-size);
  the kernel argument arrays convert their pointer-to-pointer elements
  explicitly; the device address of the scratch buffer gets the cited
  NOLINT its neighbour has (ADR-0747); the plane loop is bounded by the
  size of the header's offset table, which the analyzer could not see.

Baseline: cuda 734 to 728 by the scoped writer (picture_cuda.c 5 to 0,
integer_cambi_cuda.c 1 to 0). The header entries (common.h 3,
cuda_helper.cuh 2, picture_cuda.h 1, kernel_template.h 7) are 0 in a
full run of the lane and stay in the file: the scoped writer tightens
translation units only, and a full write is blocked by four files
outside this batch that measure above their baseline.

Behaviour: every CUDA twin was swept before and after on an RTX 4090
at --precision max (fourteen typical and stress fixtures and six small
ones, every gate feature, float_adm with debug): 427 runs, 18 192 of
18 192 values identical to the run before. Suites unchanged: CUDA build
fast 252, gpu 65 and 1 timeout (test_cuda_parity_gate_default_run, on
the base too); CPU build fast 243; HIP build fast 256, gpu 72 and 1
skipped. Golden gate: 271 passed.

T-GPU-LINT-SWEEP-HIP-CUDA-2026-09-16 carries the numbers of the whole
batch.
…2) (#1859)

* refactor(feature): bring adm.c to the lint and HISS standard (ADR-1142)

compute_adm(), the float ADM driver, was one function of 260 lines with
eight gotos and 31 clang-tidy findings. It keeps its name and signature
and is a 59-line function over helpers now:

- adm_frame_alloc() / adm_frame_free(): the band planes and the two
  index tables, in upstream's order, with upstream's stdout messages;
  every goto fail is a return and the frees happen once
- adm_scale_dwt2(): the wavelet of one scale for both pictures
- adm_scale_sums(): decouple, denominator, CSF, detail numerator, CSF,
  additive-impairment numerator, in upstream's order
- adm_accumulate_scales(): the loop over the four scales and the four
  double accumulators

Every statement that computes is carried over whole, with the type of
every temporary: float per-scale sums added into double frame sums,
aim_den before aim_num, the halving of w and h after the wavelet.

The two ADM_OPT_DEBUG_DUMP blocks are removed: they call write_image()
and PRINTF(), which nothing defines, so they could not compile. The
casts through void in init_dwt_band*() are direct casts; NULL stays
(ADR-1138); the discarded printf / fflush results are cast; adm.c
includes its own header.

clang-tidy: 31 to 0 on the cpu, cuda, hip, sycl and arm64 lanes, five
baselines tightened by the ratchet's scoped write. float_adm.c already
measured 0 on the hip, sycl and arm64 lanes, where its baseline still
held 5, 5 and 4: tightened with it. HISS: nine rows of adm.c removed,
260 to 251.

Not one bit moves. Every adm and float_adm output with debug=true and
21 option sets, and the model scores, at --precision max: 1695 of 1695
cases (271 962 values) identical on x86 for scalar, AVX2 and AVX-512,
1066 of 1066 (127 704 values) on aarch64 under qemu for scalar and
NEON. One object file changes in each build, adm.c's own. Netflix
golden gate: 271 passed, 12 skipped on x86 GCC and on aarch64 GCC,
before and after. The GPU twins do not include this file.

* 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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant