Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions changelog.d/fixed/master-ci-tsan-arm-golden.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- **master CI: ThreadSanitizer link failure and ARM golden drift fixed**.
Two regressions that rode into `master` via the admin-merge batch (per-PR CI
bypassed) are corrected: (1) the R2-9 OOM-injection test
(`test_gpu_dispatch_env_oom.cpp`) replaced the global `operator new` /
`operator delete`, which collides with the sanitizer allocator interceptors
(`ld.lld: duplicate symbol: operator new(unsigned long)`) — the override and
the test now self-skip under TSan / ASan / MSan, restoring the Sanitizers
required check; (2) the PR #1060 aarch64 scalar `-ffp-contract=off` guard on
`adm_dwt2_s` / `adm_dwt2_lo_s` shifted the akiyo `disable_enhn_gain` ADM score
on ARM (`88.030463` -> `88.030322`), failing the golden assertion on the ARM
build matrix; the scalar guard is reverted (golden restored) and the now-stale
`test_float_adm_simd` parity test removed. See ADR-1057 (Update 2026-06-27).
(3) the `test_gpu_dispatch_env_oom` test (the only test whose body is a C++ TU
using `mu_assert`) returned string-literal messages as `char *`, failing the
required `Build — Windows MSVC + CUDA` check two ways: the GCC/Clang-only
`-Wno-write-strings` flag the target carried made MSVC `cl` abort with
`D8021: invalid numeric argument '/Wno-write-strings'`, and the underlying
literal->`char *` conversion is a hard `C2440` under MSVC `/std:c++latest`
regardless. Fixed by holding the two assert messages in `static char[]`
buffers (mutable arrays decay to `char *` with no conversion; static storage
keeps the returned pointer valid) and dropping the obsolete flag — compiles
identically on MSVC/GCC/Clang. (C-sourced tests are unaffected: literal->char*
is legal in C.)
21 changes: 0 additions & 21 deletions changelog.d/fixed/neon-fma-safe-float-adm-dwt2-bitexact.md

This file was deleted.

53 changes: 3 additions & 50 deletions core/src/feature/adm_tools.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@
#include <stddef.h>
#include <string.h>

/* ADR-1057: include the generated config.h unconditionally so ARCH_AARCH64 is
* visible in this TU. The previous `#ifdef HAVE_CONFIG_H` guard was dead — the
* feature static library is not compiled with `-DHAVE_CONFIG_H`, so the macro
* never reached this file. Mirrors cpu.h, which includes config.h directly.
* The aarch64 FMA-contract guards on adm_dwt2_s / adm_dwt2_lo_s below depend on
* ARCH_AARCH64 being defined here. */
#include "config.h" // IWYU pragma: keep (provides ARCH_AARCH64 macro)
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "mem.h"
#include "adm_options.h"
Expand Down Expand Up @@ -1082,32 +1078,6 @@ void dwt2_src_indices_filt_s(int **src_ind_y, int **src_ind_x, int w, int h)
}
}

/* aarch64 FMA-contract guard (ADR-1057).
*
* On AArch64 every core has a hardware fused multiply-add, so GCC
* (`-ffp-contract=fast`) and Clang (`-ffp-contract=on`) fuse the
* `accum += filter[k] * sN` accumulations below into a single-rounded
* `fmadd`. The dispatched NEON kernel `float_adm_dwt2_neon` is deliberately
* FMA-free (explicit `vmulq` + `vaddq`, two roundings) and its TU is built
* `-ffp-contract=off`. Without this guard the NEON path and this scalar
* fallback diverge by ~1 ULP on aarch64 — the exact gap that reverted
* PR #685 (see ADR-1057) and that `test_float_adm_simd` asserts against.
*
* x86 is intentionally left untouched: GCC defaults to `-ffp-contract=off`
* on x86 (no within-statement fusion of `a*b+c`), and float-ADM DWT2 is the
* scalar path on x86 (no AVX2 DWT2 dispatch), so this is the function the
* Netflix golden gate exercises. Gating the guard on `ARCH_AARCH64` keeps the
* x86 golden bytes byte-identical while making the aarch64 scalar fallback
* bit-exact with the NEON kernel. Clang on aarch64 honours the `#pragma`;
* GCC honours the function attribute. */
#if defined(ARCH_AARCH64) && ARCH_AARCH64
#if defined(__clang__)
#pragma clang fp contract(off)
#endif
#if defined(__GNUC__) && !defined(__clang__)
__attribute__((optimize("-ffp-contract=off")))
#endif
#endif
int adm_dwt2_s(const float *src, const adm_dwt_band_t_s *dst, int **ind_y, int **ind_x, int w,
int h, int src_stride, int dst_stride)
{
Expand Down Expand Up @@ -1205,16 +1175,6 @@ int adm_dwt2_s(const float *src, const adm_dwt_band_t_s *dst, int **ind_y, int *
return 0;
}

/* aarch64 FMA-contract guard (ADR-1057) — see adm_dwt2_s above. `adm_dwt2_lo`
* has no SIMD twin (always scalar), but keeping the low-pass-only DWT2
* FMA-free on aarch64 holds the scale-0 lo-pass numerics consistent with the
* full DWT2 and with x86. The GCC attribute is per-function; the Clang
* `#pragma` set at adm_dwt2_s is still in effect here. */
#if defined(ARCH_AARCH64) && ARCH_AARCH64
#if defined(__GNUC__) && !defined(__clang__)
__attribute__((optimize("-ffp-contract=off")))
#endif
#endif
int adm_dwt2_lo_s(const float *src, const adm_dwt_band_t_s *dst, int **ind_y, int **ind_x, int w,
int h, int src_stride, int dst_stride)
{
Expand Down Expand Up @@ -1273,13 +1233,6 @@ int adm_dwt2_lo_s(const float *src, const adm_dwt_band_t_s *dst, int **ind_y, in
return 0;
}

/* Restore Clang's default FP contraction for the remaining (double-precision
* DWT2 + buffer-copy) functions; the `contract(off)` region set at adm_dwt2_s
* applied only to the two single-precision DWT2 kernels on aarch64. */
#if defined(ARCH_AARCH64) && ARCH_AARCH64 && defined(__clang__)
#pragma clang fp contract(on)
#endif

int adm_dwt2_d(const double *src, const adm_dwt_band_t_d *dst, int **ind_y, int **ind_x, int w,
int h, int src_stride, int dst_stride)
{
Expand Down
1 change: 0 additions & 1 deletion core/src/feature/arm64/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ with the matching change to the other halves in the **same PR**:
| **SSIMULACRA 2 SIMD** (ADR-0161 / 0162 / 0163 / 0213 / 0252) | `ssimulacra2_neon.c` + `ssimulacra2_sve2.c` + `../x86/ssimulacra2_avx2.c` + `../x86/ssimulacra2_avx512.c` + `ssimulacra2_host_neon.c` + `../x86/ssimulacra2_host_avx2.c` + scalar `../ssimulacra2.c` + Vulkan host-path `../vulkan/ssimulacra2_vulkan.c` |
| **CAMBI calculate_c_values_row** (ADR-0452) | `cambi_neon.c` (`calculate_c_values_row_neon`) + `../x86/cambi_avx2.c` (`calculate_c_values_row_avx2`) + `../x86/cambi_avx512.c` (`calculate_c_values_row_avx512`) + scalar in `../cambi.c` (`calculate_c_values_row`). Every cambi inner-loop function ported to AVX2 **must** have AVX-512 + NEON siblings in the **same PR**. NEON uses vectorised mask-zero detection (vmaxvq_u16) + scalar per-pixel inner loop for bit-exact output (no gather instruction on NEON). Tested in `../../test/test_cambi_simd.c`. |
| **Motion v2 NEON** (ADR-0145) | `motion_v2_neon.c` uses **arithmetic** right-shift (`vshrq_n_s64(v, 16)` / `vshlq_s64(v, -(int64_t)bpc)`); matches scalar. Sister `../x86/motion_v2_avx2.c` uses `_mm256_srlv_epi64` (logical) — knowingly out-of-spec until the AVX2 audit. **Do NOT port the AVX2 logical pattern here.** 4-lane stride + scalar tails on both sides of the row are load-bearing for the x_conv edge-mirror contract. |
| **float-ADM DWT2** (ADR-1057) | `float_adm_dwt2_neon.c` (FMA-free: `vmulq_laneq_f32` + `vaddq_f32`, **never** `vmlaq*` / `vfmaq*`; meson lib `arm64_adm_dwt2_neon_lib` built `-ffp-contract=off`) + scalar `../adm_tools.c` (`adm_dwt2_s` / `adm_dwt2_lo_s`). The scalar half carries an **aarch64-only `-ffp-contract=off` guard** (`#pragma clang fp contract(off)` + GCC `optimize` attribute) — without it the compiler fuses the scalar `accum += a*b` into a single-rounded `fmadd` on aarch64 and the NEON path diverges by ~1 ULP. `adm_tools.c` must keep its unconditional `#include "config.h"` (provides `ARCH_AARCH64`; the old `#ifdef HAVE_CONFIG_H` was dead). x86 is inert (guard gated on `ARCH_AARCH64`; GCC x86 default is already `-ffp-contract=off`) so the golden gate is untouched. Bit-exactness asserted by `../../test/test_float_adm_simd.c`. |

The complete invariants live in [../AGENTS.md
§"Rebase-sensitive invariants"](../AGENTS.md).
Expand Down
39 changes: 6 additions & 33 deletions core/test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1309,34 +1309,6 @@ test_ms_ssim_decimate = executable('test_ms_ssim_decimate',
)
endif

# float-ADM NEON DWT2 bit-exactness parity test (ADR-1057).
# Asserts float_adm_dwt2_neon == adm_dwt2_s byte-for-byte. The kernel lives in
# the `arm64_adm_dwt2_neon_lib` carve-out (compiled `-ffp-contract=off`) inside
# platform_specific_cpu_objects; the scalar reference is in the feature static
# lib. The test self-skips the NEON comparison on non-aarch64 (kernel absent),
# so it builds and runs on every arch. `_simd_strict_fp_args` keeps the test TU
# itself free of host FMA contraction.
test_float_adm_simd = executable('test_float_adm_simd',
['test.c', 'test_float_adm_simd.c',
'../src/mem.c', '../src/picture.c', '../src/ref.c',
'../src/dict.c', '../src/predict.c',
'../src/metadata_handler.cpp', '../src/thread_locale.c', '../src/gpu_picture_pool.cpp', dnn_sources],
include_directories : [libvmaf_inc, test_inc, include_directories('../src/'),
include_directories('../src/feature/'), dnn_inc],
c_args : vmaf_cflags_common + dnn_defines + _simd_strict_fp_args,
dependencies : [math_lib, stdatomic_dependency, pthread_dependency, thread_lib,
gpu_all_deps, dnn_deps],
objects : [
common_cuda_objects,
platform_specific_cpu_objects,
libvmaf_cpu_static_lib.extract_all_objects(recursive: true),
libvmaf_feature_static_lib.extract_all_objects(recursive: true),
log_cpp23_test_objects,
libsvm_static_lib.extract_all_objects(recursive: true),
] + wave8_opt_only_objects,
)
test('test_float_adm_simd', test_float_adm_simd, suite : ['fast', 'simd'])

test_locale_handling = executable('test_locale_handling',
['test.c', 'test_locale_handling.c'],
include_directories : [
Expand Down Expand Up @@ -2074,11 +2046,12 @@ test_gpu_dispatch_env_oom = executable('test_gpu_dispatch_env_oom',
['test.c', 'test_gpu_dispatch_env_oom.cpp'],
include_directories : [libvmaf_inc, test_inc,
include_directories('../src/')],
# The shared C `mu_assert` macro (test.h) returns its message argument as
# `char *`; passing a C++ string literal trips -Wwrite-strings. The macro
# is upstream-owned and shared by every test, so suppress the diagnostic
# for this C++ TU rather than fork the harness.
cpp_args : ['-Wno-write-strings'],
# No -Wno-write-strings hack: this C++ TU holds its `mu_assert` messages in
# `static char[]` buffers (see the .cpp) instead of returning string
# literals as `char *`, so there is nothing to suppress — and the old flag
# was both unnecessary on GCC/Clang and broke the MSVC build (cl rejected
# `/Wno-write-strings` with D8021, and the underlying literal->char*
# conversion was a hard C2440 there anyway).
dependencies: [pthread_dependency, stdatomic_dependency],
objects : [gpu_dispatch_env_cpp23_lib.extract_all_objects(recursive: true)],
)
Expand Down
Loading
Loading