Skip to content

fix: clear the -Denable_asm=false and aarch64 build warnings, and lint vif_neon.c to zero - #1483

Merged
lusoris merged 7 commits into
masterfrom
fix/no-asm-test-warnings
Sep 19, 2026
Merged

lusoris merged 7 commits into
masterfrom
fix/no-asm-test-warnings

Conversation

@lusoris

@lusoris lusoris commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds that CI does not gate on were not warning-free, and one NEON file had never been linted.

  • -Denable_asm=false builds emitted about 30 -Wunused-function / -Wunused-const-variable / -Wunused-variable warnings in 10 SIMD test files. Helpers were defined unconditionally while every caller sat under an ISA guard. Each helper now sits under exactly the union of its callers' guards; nothing is deleted or suppressed. Warnings go from 30 to 0, and the set of tests that runs is unchanged on every configuration: -Denable_asm=false x86-64 133/133, default x86-64 137/137, aarch64 under qemu 135/135.
  • aarch64 builds warned twice in vif_neon.c. Both VIF statistic kernels advanced an i_dst_stride counter every row and never read it; upstream has the same dead code. It is removed.
  • vif_neon.c had 36 clang-tidy findings, which no CI lane measures because the cpu lane is x86-only. Touching the file meant cleaning it: the four kernels are now row loops over static FORCE_INLINE helpers, in the style of vif_avx2.c, and the missing vif_neon.h include fixes the linkage findings. It is at 0 findings and 0 NOLINTs, and cppcheck is clean. NEON output is byte-identical: 68/68 --precision max runs match the previous binary per compiler (GCC and Clang cross builds under qemu). The inputs cover 8/10/12-bit, odd and tiny sizes, and four VIF and model configurations. NEON also still equals scalar on all 34 pairs.
  • test_calculate_c_values_scalar_avx2_parity stays under the 60-line HISS-04 limit: its AVX2 leg is now an x86-only helper, and the test releases its pictures before reporting a mismatch.
  • .standards-baseline.json is re-recorded with the pinned engine from a clean clone. The guards moved three long test functions the line-keyed baseline tracks; debt fell from 1,627 to 1,623.

no docs needed: test scaffolding, a dead-variable removal and a behaviour-preserving refactor; no user-visible change.

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: pre-commit and praetorctl audit pass; clang-tidy 0 findings and 0 uncited NOLINTs on every touched file (CPU lane, and a Clang aarch64 cross build for vif_neon.c).
  • Unit tests pass: meson test -C build: counts as above; test_vif_neon passes on both aarch64 cross builds.
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2: 0; byte-identical NEON output before and after, and vs scalar.
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below: NEON only; the x86 twins were already split and lint-clean.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header: no new files.
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE:: not breaking.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/: no ADR.

Bug-status hygiene (ADR-0165)

  • docs/state.md: T-NO-ASM-SIMD-TEST-WARNINGS-2026-09-18 closed.

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: no score changes.

Deep-dive deliverables (ADR-0108)

  • Research digest — no digest needed: trivial; guard placement, dead-code removal and a helper split.
  • Decision matrix — no alternatives: only-one-way fix.
  • AGENTS.md invariant note — no rebase-sensitive invariants beyond the rebase-notes entries below.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/fixed/no-asm-simd-test-unused-warnings.md.
  • Rebase note — docs/rebase-notes.md: helper guards mirror their run_tests() call sites; vif_neon.c is split into helpers.

Reproducer

meson setup build-noasm core -Denable_asm=false -Denable_cuda=false -Denable_sycl=false -Db_lto=false
ninja -C build-noasm 2>&1 | grep -c 'warning:'        # 0
meson setup build-aarch64 core --cross-file aarch64.ini   # gcc cross + qemu-aarch64-static
ninja -C build-aarch64 2>&1 | grep -c 'warning:'      # 0
meson test -C build-aarch64 --suite fast

Known follow-ups

  • No clang-tidy lane measures arm64 (NEON) sources at all; ADR-1142 says the whole tree is ratcheted. An aarch64 lane would close that gap and needs a CI-gate decision.

@github-actions github-actions Bot added the type:bug Something isn't working label Sep 18, 2026
Comment thread core/test/test_cambi.c
/* Intentional bit-exact compare: AVX2 and scalar paths must produce
* identical float results per the CAMBI parity contract. */
mu_assert("scalar vs avx2 calculate_c_values parity (bit-exact)",
c_scalar[i] == c_avx2[i]); /* bit-exact SIMD parity assertion */
…ings

A -Denable_asm=false build emitted ~30 -Wunused-function /
-Wunused-const-variable / -Wunused-variable warnings across ten SIMD
parity-test files under core/test/: scalar-reference helpers, fixture
builders, and lookup tables were defined unconditionally while every
caller sat under an ISA guard (ARCH_X86, HAVE_AVX512, ARCH_AARCH64, or
the ARCH_X86 || ARCH_AARCH64 union run_tests() already dispatches on).

Move each helper under the same guard its callers use, walking the
full dependency chain so the warning does not just relocate to a
pick_*/ref_* helper one level down. test_ssimulacra2_simd.c needed one
guard spanning its whole pick/ref/test section for that reason;
test_cambi.c's histograms_a became a local declaration inside the
existing #if ARCH_X86 block instead of an unconditional one. Nothing
deleted, no (void) casts or [[maybe_unused]] suppressions.

Files: test_vif_simd.c, test_ssimulacra2_simd.c, test_speed_simd.c,
test_psnr_hvs_simd.c, test_ms_ssim_decimate.c, test_motion_v2_simd.c,
test_iqa_convolve.c, test_integer_ssim_simd.c, test_cambi_simd.c,
test_cambi.c.

Verified zero warnings and an identical test count on:
- -Denable_asm=false x86-64 (133/133 fast tests, was 30 warnings)
- default x86-64 (137/137 fast tests, 0 warnings before and after)
- aarch64 cross build under qemu-aarch64-static (135/135 fast tests;
  the two pre-existing, unrelated vif_neon.c -Wunused-but-set-variable
  warnings are untouched)

Closes T-NO-ASM-SIMD-TEST-WARNINGS-2026-09-18 (docs/state.md).
…stic kernels

vif_statistic_8_neon() and vif_statistic_16_neon() advanced an i_dst_stride
counter every row and never read it, nor the stride it was built from; GCC
reports -Wunused-but-set-variable on every aarch64 build. Upstream carries
the same dead counters. No arithmetic changes.
…tidy findings

The four NEON VIF kernels were single functions of 131 to 322 lines built
from NEON_FILTER_* statement macros, which gave 36 clang-tidy findings: 18
macro-parentheses, 6 isolate-declaration, 4 function-size, 4
use-internal-linkage and 4 implicit-widening. No CI lane measures the arm64
tree, so the file was never held to ADR-1142 / ADR-0141. This branch touches
it, so it has to end clean.

Each kernel is now a row loop over static FORCE_INLINE helpers. They issue
the same intrinsics in the same lane order as before: per-plane vertical and
horizontal passes over small lane structs. The first filter tap has its own
*_init helper wherever the macro code seeded an accumulator with a plain
product. The macros are gone. vif_neon.c now includes vif_neon.h, which gives
the four dispatched kernels their prototypes instead of making them static.
The decimation index is now computed in ptrdiff_t.

The rewrite also clears cppcheck's 42 shadowVariable and 6
constVariablePointer reports. The two statistic kernels take the Research-2045
constParameterPointer exception that their x86 twins already carry, because
the VifState callback type requires the mutable parameter.

tidy-ratchet --lane cpu --build-dir build-aarch64-clang --only vif_neon.c:
36 warnings before, 0 after, with no NOLINT and no compile failure.

Bit-exactness: CLI binaries built before and after this change, with GCC and
with Clang, were run under qemu-aarch64 over 34 input/config pairs, each with
NEON and with --cpumask scalar dispatch. Inputs: the Netflix src01 pair, 8-,
10- and 12-bit inputs, and 570x318, 571x317 and 61x35 frames. Configs:
--feature vif, vif debug with vif_enhn_gain_limit=1.0, the default model and
vmaf_v0.6.1, all at --precision max. All 68 JSON outputs per compiler are
byte-identical to the previous binary (excluding fps and version), and NEON
matches scalar on all 34 pairs. test_vif_neon and the fast suite (135/135)
pass on both cross builds, and GCC and Clang compile the file with
-Wall -Wextra and no diagnostics.
Guarding histograms_a for -Denable_asm=false builds pushed the test to 61
lines. The AVX2 leg now lives in a helper compiled only for x86, which also
lets the test release its pictures before reporting a mismatch.
…ards

The guards moved three long test functions the line-keyed baseline tracks
(ref_calc_psnrhvs, ref_picture_to_linear_rgb, test_ptlr_one), so the audit
reported them as new while debt fell from 1,627 to 1,623 infractions.
Recorded with the pinned engine (praetor e4b35cb) from a clean clone.
@lusoris
lusoris force-pushed the fix/no-asm-test-warnings branch from 04fab82 to f90e0a6 Compare September 19, 2026 08:47
@lusoris
lusoris merged commit 9b8ec37 into master Sep 19, 2026
80 checks passed
@lusoris
lusoris deleted the fix/no-asm-test-warnings branch September 19, 2026 09:38
lusoris added a commit that referenced this pull request Sep 30, 2026
At the coarsest of CAMBI's five scales a wide, short input has fewer
rows than half the window plus one (pad_size + 1 rows): 1920x128 leaves
8 rows against pad_size 11. The c-values walk ran its first pass and its
top edge for pad_size rows and started its bottom edge at height - pad_size
whatever the height, so it read image and mask rows past both ends of the
frame and wrote them into c_values (Netflix/vmaf#1628). Under ASan, master
fails at 1920x2, 1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every
dispatch level; a release build aborts or segfaults on 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in
cambi.c and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan,
AVX-512 and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

With at least pad_size + 1 rows at every scale the bounds are the old
ones. cambi and cambi=full_ref=true scores are identical to master at
--precision max on the Netflix pairs, both checkerboard pairs, 200
frames of BBB 3840x2160 and noise and ramp inputs of standard sizes, at
the AVX-512, AVX2 and C dispatch levels. Frames with fewer than pad_size + 1
rows at the coarsest scale that completed on scalar master while reading
out of bounds now score differently (e.g. 3-frame 8-bit ramps: 3840x128
moves 19.544347 -> 19.512269, 1920x160 moves 22.270340 -> 22.267602,
and 3840x256 moves 21.778393 -> 21.769946).

test_calculate_c_values_short_frame runs every driver the host has on
views of 1 to 10 rows into a taller picture: rows outside the frame
must keep a sentinel, and every in-frame c-value must equal a
from-scratch window histogram. On master it fails for 1 to 4 rows on
all five drivers without a sanitizer. test_cambi_stage_simd now also
sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check had stopped running again: #1483 merged
first and #1479 lost its code change when rebased onto #1483, leaving
only the comment behind vmaf_get_cpu_flags(). It now reads CPUID via
vmaf_get_cpu_flags_x86().

docs/metrics/cambi.md gains a frame-size section, and its window_size
default is corrected to the code's 65. docs/state.md closes
T-CAMBI-SHORT-FRAME-OOB-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-REVERTED-2026-09-30, and opens
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 as RC2 stabilisation.
core/src/feature/x86/cambi_avx2.c carries an ADR-1256 cppcheck suppression
for the mutable callback ABI.
lusoris added a commit that referenced this pull request Sep 30, 2026
At the coarsest of CAMBI's five scales a wide, short input can have no more
rows than pad_size, half the window: 1920x128 leaves 8 rows against pad_size
11, and with the default window every height up to 176 at 1920 wide (352 at
3840) is affected. The c-values walk ran its first pass and its top edge for
pad_size rows and started its bottom edge at height - pad_size whatever the
height, so it read image and mask rows past both ends of the frame and wrote
them into c_values (Netflix/vmaf#1628). Under ASan, master fails at 1920x2,
1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every dispatch level; a
release build aborts or segfaults on 1920x64, 1920x160, 2560x224, 3840x256
and 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in cambi.c
and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan, AVX-512
and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

The same walks had the column version of the bug: the scalar and AVX2-mirror
first column loops ran to pad_size whatever the width, so on a scale with
fewer than pad_size columns (default window: up to 80 wide at 1080 high, 160
at 1920 high) they counted columns past the frame. decimate() works in place,
so those columns hold the previous scale's pixels, inside the stride, where
no sanitizer sees the read. The shared SIMD walk never visited them, so
--cpumask 63 and the default dispatch disagreed, and so did the CUDA, HIP and
Metal twins, which run the scalar walk on the host. All eight loops now run
to MIN(pad_size, width). This column bound goes beyond upstream
Netflix/vmaf#1629, which bounds only the rows. Upstream has the same column
loops.

Scores are identical to master at --precision max, at the AVX-512, AVX2 and
C dispatch levels, for every size the old loops handled: the Netflix pairs,
both checkerboard pairs, BBB 3840x2160, and ramp and noise inputs at
1920x176, 2560x240, 3840x352, 96x1080 and 1920x1080. Short frames that master
completed on the C path while reading outside the frame change (3840x128
horizontal ramp 19.544347 -> 19.512269). Scores change for frames narrower
than pad_size at some scale (measured: 64x1920 vertical ramp master
14.975700714938673 vs branch 14.964394451743877 on the C path; the SIMD
paths already agreed, giving 14.964394451743877 on both; another vertical
ramp variant moves from 16.141046 to 16.131541; cambi_cuda on the RTX 4090
and cambi_hip on gfx1036 give the new CPU score bit for bit). ADR-1393
records clipping over rejecting such frames in init(); Research-2132 has the
measurements.

test_calculate_c_values_short_frame and test_calculate_c_values_narrow_frame
run every driver the host has on 1 to 10 rows and 1 to pad + 2 columns
against a from-scratch clipped-window histogram and a sentinel margin. On
master sources the short test fails for 1 to 4 rows on all five drivers
without a sanitizer; with the column loops unbounded the narrow test fails on
the scalar walk, and with only the AVX2 mirror unbounded on that walk.
test_cambi_stage_simd also sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check never ran on master: #1479 changed its gate to
vmaf_get_cpu_flags_x86(), but #1483 merged first, and when #1479 was rebased
onto it the branch kept its comment and took #1483's helper with the old
vmaf_get_cpu_flags() gate. It now reads CPUID.

docs/state.md closes T-CAMBI-SHORT-FRAME-OOB-2026-09-30,
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-LOST-IN-REBASE-2026-09-30, and opens
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 (fix in #1647).
calculate_c_values_scan_avx2() carries a cited cppcheck suppression: its
non-const picture comes from upstream's VmafCalcCValues callback type.
core/test/meson.build raises test_gpu_public_header_docs timeout to 120s
for Windows MinGW64.
lusoris added a commit that referenced this pull request Oct 1, 2026
At the coarsest of CAMBI's five scales a wide, short input can have no more
rows than pad_size, half the window: 1920x128 leaves 8 rows against pad_size
11, and with the default window every height up to 176 at 1920 wide (352 at
3840) is affected. The c-values walk ran its first pass and its top edge for
pad_size rows and started its bottom edge at height - pad_size whatever the
height, so it read image and mask rows past both ends of the frame and wrote
them into c_values (Netflix/vmaf#1628). Under ASan, master fails at 1920x2,
1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every dispatch level; a
release build aborts or segfaults on 1920x64, 1920x160, 2560x224, 3840x256
and 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in cambi.c
and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan, AVX-512
and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

The same walks had the column version of the bug: the scalar and AVX2-mirror
first column loops ran to pad_size whatever the width, so on a scale with
fewer than pad_size columns (default window: up to 80 wide at 1080 high, 160
at 1920 high) they counted columns past the frame. decimate() works in place,
so those columns hold the previous scale's pixels, inside the stride, where
no sanitizer sees the read. The shared SIMD walk never visited them, so
--cpumask 63 and the default dispatch disagreed, and so did the CUDA, HIP and
Metal twins, which run the scalar walk on the host. All eight loops now run
to MIN(pad_size, width). This column bound goes beyond upstream
Netflix/vmaf#1629, which bounds only the rows. Upstream has the same column
loops.

Scores are identical to master at --precision max, at the AVX-512, AVX2 and
C dispatch levels, for every size the old loops handled: the Netflix pairs,
both checkerboard pairs, BBB 3840x2160, and ramp and noise inputs at
1920x176, 2560x240, 3840x352, 96x1080 and 1920x1080. Short frames that master
completed on the C path while reading outside the frame change (3840x128
horizontal ramp 19.544347 -> 19.512269). Scores change for frames narrower
than pad_size at some scale (measured: 64x1920 vertical ramp master
14.975700714938673 vs branch 14.964394451743877 on the C path; the SIMD
paths already agreed, giving 14.964394451743877 on both; another vertical
ramp variant moves from 16.141046 to 16.131541; cambi_cuda on the RTX 4090
and cambi_hip on gfx1036 give the new CPU score bit for bit). ADR-1393
records clipping over rejecting such frames in init(); Research-2132 has the
measurements.

test_calculate_c_values_short_frame and test_calculate_c_values_narrow_frame
run every driver the host has on 1 to 10 rows and 1 to pad + 2 columns
against a from-scratch clipped-window histogram and a sentinel margin. On
master sources the short test fails for 1 to 4 rows on all five drivers
without a sanitizer; with the column loops unbounded the narrow test fails on
the scalar walk, and with only the AVX2 mirror unbounded on that walk.
test_cambi_stage_simd also sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check never ran on master: #1479 changed its gate to
vmaf_get_cpu_flags_x86(), but #1483 merged first, and when #1479 was rebased
onto it the branch kept its comment and took #1483's helper with the old
vmaf_get_cpu_flags() gate. It now reads CPUID.

docs/state.md closes T-CAMBI-SHORT-FRAME-OOB-2026-09-30,
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-LOST-IN-REBASE-2026-09-30, and opens
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 (fix in #1647).
calculate_c_values_scan_avx2() carries a cited cppcheck suppression: its
non-const picture comes from upstream's VmafCalcCValues callback type.
core/test/meson.build raises test_gpu_public_header_docs timeout to 120s
for Windows MinGW64.
lusoris added a commit that referenced this pull request Oct 1, 2026
At the coarsest of CAMBI's five scales a wide, short input can have no more
rows than pad_size, half the window: 1920x128 leaves 8 rows against pad_size
11, and with the default window every height up to 176 at 1920 wide (352 at
3840) is affected. The c-values walk ran its first pass and its top edge for
pad_size rows and started its bottom edge at height - pad_size whatever the
height, so it read image and mask rows past both ends of the frame and wrote
them into c_values (Netflix/vmaf#1628). Under ASan, master fails at 1920x2,
1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every dispatch level; a
release build aborts or segfaults on 1920x64, 1920x160, 2560x224, 3840x256
and 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in cambi.c
and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan, AVX-512
and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

The same walks had the column version of the bug: the scalar and AVX2-mirror
first column loops ran to pad_size whatever the width, so on a scale with
fewer than pad_size columns (default window: up to 80 wide at 1080 high, 160
at 1920 high) they counted columns past the frame. decimate() works in place,
so those columns hold the previous scale's pixels, inside the stride, where
no sanitizer sees the read. The shared SIMD walk never visited them, so
--cpumask 63 and the default dispatch disagreed, and so did the CUDA, HIP and
Metal twins, which run the scalar walk on the host. All eight loops now run
to MIN(pad_size, width). This column bound goes beyond upstream
Netflix/vmaf#1629, which bounds only the rows. Upstream has the same column
loops.

Scores are identical to master at --precision max, at the AVX-512, AVX2 and
C dispatch levels, for every size the old loops handled: the Netflix pairs,
both checkerboard pairs, BBB 3840x2160, and ramp and noise inputs at
1920x176, 2560x240, 3840x352, 96x1080 and 1920x1080. Short frames that master
completed on the C path while reading outside the frame change (3840x128
horizontal ramp 19.544347 -> 19.512269). Scores change for frames narrower
than pad_size at some scale (measured: 64x1920 vertical ramp master
14.975700714938673 vs branch 14.964394451743877 on the C path; the SIMD
paths already agreed, giving 14.964394451743877 on both; another vertical
ramp variant moves from 16.141046 to 16.131541; cambi_cuda on the RTX 4090
and cambi_hip on gfx1036 give the new CPU score bit for bit). ADR-1393
records clipping over rejecting such frames in init(); Research-2132 has the
measurements.

test_calculate_c_values_short_frame and test_calculate_c_values_narrow_frame
run every driver the host has on 1 to 10 rows and 1 to pad + 2 columns
against a from-scratch clipped-window histogram and a sentinel margin. On
master sources the short test fails for 1 to 4 rows on all five drivers
without a sanitizer; with the column loops unbounded the narrow test fails on
the scalar walk, and with only the AVX2 mirror unbounded on that walk.
test_cambi_stage_simd also sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check never ran on master: #1479 changed its gate to
vmaf_get_cpu_flags_x86(), but #1483 merged first, and when #1479 was rebased
onto it the branch kept its comment and took #1483's helper with the old
vmaf_get_cpu_flags() gate. It now reads CPUID.

docs/state.md closes T-CAMBI-SHORT-FRAME-OOB-2026-09-30,
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-LOST-IN-REBASE-2026-09-30, and opens
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 (fix in #1647).
calculate_c_values_scan_avx2() carries a cited cppcheck suppression: its
non-const picture comes from upstream's VmafCalcCValues callback type.
core/test/meson.build raises test_gpu_public_header_docs timeout to 120s
for Windows MinGW64.
lusoris added a commit that referenced this pull request Oct 1, 2026
At the coarsest of CAMBI's five scales a wide, short input can have no more
rows than pad_size, half the window: 1920x128 leaves 8 rows against pad_size
11, and with the default window every height up to 176 at 1920 wide (352 at
3840) is affected. The c-values walk ran its first pass and its top edge for
pad_size rows and started its bottom edge at height - pad_size whatever the
height, so it read image and mask rows past both ends of the frame and wrote
them into c_values (Netflix/vmaf#1628). Under ASan, master fails at 1920x2,
1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every dispatch level; a
release build aborts or segfaults on 1920x64, 1920x160, 2560x224, 3840x256
and 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in cambi.c
and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan, AVX-512
and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

The same walks had the column version of the bug: the scalar and AVX2-mirror
first column loops ran to pad_size whatever the width, so on a scale with
fewer than pad_size columns (default window: up to 80 wide at 1080 high, 160
at 1920 high) they counted columns past the frame. decimate() works in place,
so those columns hold the previous scale's pixels, inside the stride, where
no sanitizer sees the read. The shared SIMD walk never visited them, so
--cpumask 63 and the default dispatch disagreed, and so did the CUDA, HIP and
Metal twins, which run the scalar walk on the host. All eight loops now run
to MIN(pad_size, width). This column bound goes beyond upstream
Netflix/vmaf#1629, which bounds only the rows. Upstream has the same column
loops.

Scores are identical to master at --precision max, at the AVX-512, AVX2 and
C dispatch levels, for every size the old loops handled: the Netflix pairs,
both checkerboard pairs, BBB 3840x2160, and ramp and noise inputs at
1920x176, 2560x240, 3840x352, 96x1080 and 1920x1080. Short frames that master
completed on the C path while reading outside the frame change (3840x128
horizontal ramp 19.544347 -> 19.512269). Scores change for frames narrower
than pad_size at some scale (measured: 64x1920 vertical ramp master
14.975700714938673 vs branch 14.964394451743877 on the C path; the SIMD
paths already agreed, giving 14.964394451743877 on both; another vertical
ramp variant moves from 16.141046 to 16.131541; cambi_cuda on the RTX 4090
and cambi_hip on gfx1036 give the new CPU score bit for bit). ADR-1393
records clipping over rejecting such frames in init(); Research-2132 has the
measurements.

test_calculate_c_values_short_frame and test_calculate_c_values_narrow_frame
run every driver the host has on 1 to 10 rows and 1 to pad + 2 columns
against a from-scratch clipped-window histogram and a sentinel margin. On
master sources the short test fails for 1 to 4 rows on all five drivers
without a sanitizer; with the column loops unbounded the narrow test fails on
the scalar walk, and with only the AVX2 mirror unbounded on that walk.
test_cambi_stage_simd also sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check never ran on master: #1479 changed its gate to
vmaf_get_cpu_flags_x86(), but #1483 merged first, and when #1479 was rebased
onto it the branch kept its comment and took #1483's helper with the old
vmaf_get_cpu_flags() gate. It now reads CPUID.

docs/state.md closes T-CAMBI-SHORT-FRAME-OOB-2026-09-30,
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-LOST-IN-REBASE-2026-09-30, and opens
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 (fix in #1647).
calculate_c_values_scan_avx2() carries a cited cppcheck suppression: its
non-const picture comes from upstream's VmafCalcCValues callback type.
core/test/meson.build raises test_gpu_public_header_docs timeout to 120s
for Windows MinGW64.
lusoris added a commit that referenced this pull request Oct 1, 2026
* fix(cambi): keep the c-values walks inside short and narrow frames

At the coarsest of CAMBI's five scales a wide, short input can have no more
rows than pad_size, half the window: 1920x128 leaves 8 rows against pad_size
11, and with the default window every height up to 176 at 1920 wide (352 at
3840) is affected. The c-values walk ran its first pass and its top edge for
pad_size rows and started its bottom edge at height - pad_size whatever the
height, so it read image and mask rows past both ends of the frame and wrote
them into c_values (Netflix/vmaf#1628). Under ASan, master fails at 1920x2,
1920x16, 1920x64, 1920x128, 1920x160 and 3840x128 at every dispatch level; a
release build aborts or segfaults on 1920x64, 1920x160, 2560x224, 3840x256
and 1921x129 4:4:4.

Port the upstream fix (Netflix/vmaf#1629) to calculate_c_values() in cambi.c
and to the upstream-mirror calculate_c_values_avx2(), and give
cambi_calculate_c_values_frame(), the walk the dispatched AVX2 scan, AVX-512
and NEON drivers share, the same three bounds:

    first pass   i < MIN(pad_size, height)
    top edge     i < MIN(pad_size + 1, height)
    bottom edge  i = MAX(height - pad_size, 0)

The same walks had the column version of the bug: the scalar and AVX2-mirror
first column loops ran to pad_size whatever the width, so on a scale with
fewer than pad_size columns (default window: up to 80 wide at 1080 high, 160
at 1920 high) they counted columns past the frame. decimate() works in place,
so those columns hold the previous scale's pixels, inside the stride, where
no sanitizer sees the read. The shared SIMD walk never visited them, so
--cpumask 63 and the default dispatch disagreed, and so did the CUDA, HIP and
Metal twins, which run the scalar walk on the host. All eight loops now run
to MIN(pad_size, width). This column bound goes beyond upstream
Netflix/vmaf#1629, which bounds only the rows. Upstream has the same column
loops.

Scores are identical to master at --precision max, at the AVX-512, AVX2 and
C dispatch levels, for every size the old loops handled: the Netflix pairs,
both checkerboard pairs, BBB 3840x2160, and ramp and noise inputs at
1920x176, 2560x240, 3840x352, 96x1080 and 1920x1080. Short frames that master
completed on the C path while reading outside the frame change (3840x128
horizontal ramp 19.544347 -> 19.512269). Scores change for frames narrower
than pad_size at some scale (measured: 64x1920 vertical ramp master
14.975700714938673 vs branch 14.964394451743877 on the C path; the SIMD
paths already agreed, giving 14.964394451743877 on both; another vertical
ramp variant moves from 16.141046 to 16.131541; cambi_cuda on the RTX 4090
and cambi_hip on gfx1036 give the new CPU score bit for bit). ADR-1393
records clipping over rejecting such frames in init(); Research-2132 has the
measurements.

test_calculate_c_values_short_frame and test_calculate_c_values_narrow_frame
run every driver the host has on 1 to 10 rows and 1 to pad + 2 columns
against a from-scratch clipped-window histogram and a sentinel margin. On
master sources the short test fails for 1 to 4 rows on all five drivers
without a sanitizer; with the column loops unbounded the narrow test fails on
the scalar walk, and with only the AVX2 mirror unbounded on that walk.
test_cambi_stage_simd also sweeps 1-row and pad-row frames.

test_cambi's AVX2 parity check never ran on master: #1479 changed its gate to
vmaf_get_cpu_flags_x86(), but #1483 merged first, and when #1479 was rebased
onto it the branch kept its comment and took #1483's helper with the old
vmaf_get_cpu_flags() gate. It now reads CPUID.

docs/state.md closes T-CAMBI-SHORT-FRAME-OOB-2026-09-30,
T-CAMBI-NARROW-FRAME-COLUMNS-2026-09-30 and
T-CAMBI-AVX2-PARITY-GATE-LOST-IN-REBASE-2026-09-30, and opens
T-CLI-PRE-REGISTRATION-OPTS-DICT-LEAK-2026-09-30 (fix in #1647).
calculate_c_values_scan_avx2() carries a cited cppcheck suppression: its
non-const picture comes from upstream's VmafCalcCValues callback type.
core/test/meson.build raises test_gpu_public_header_docs timeout to 120s
for Windows MinGW64.

* 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:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants