From 40575b35612fa827ada4aa80b30bef032b61b0d6 Mon Sep 17 00:00:00 2001 From: Wan-Teh Chang Date: Wed, 1 Jul 2026 09:24:28 -0700 Subject: [PATCH] I010ToNV12: dispatch Convert16To8Row on halfwidth The Convert16To8Row function pointer in I010ToNV12 is only ever called with halfwidth for the chroma planes (the Y plane goes through Convert16To8Plane which has its own dispatch), but its SIMD variants were selected based on the alignment of the full luma width. When width is a multiple of 64 but halfwidth is not, the bare AVX512BW kernel was selected and over-read the chroma sources and over-wrote the temporary row buffer. Match the MergeUVRow dispatch in the same function and key on halfwidth. Add a regression test that places the chroma sources against an inaccessible page so the previous over-read faults on AVX512BW hardware. Bug: chromium:514748734 Change-Id: I4c2a42857828cdcc6505ebaa65d5b6a95b0f7e55 --- source/convert.cc | 8 ++-- unit_test/convert_test.cc | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/source/convert.cc b/source/convert.cc index fbef68f57..0caec6ef5 100644 --- a/source/convert.cc +++ b/source/convert.cc @@ -682,7 +682,7 @@ int I010ToNV12(const uint16_t* src_y, #if defined(HAS_CONVERT16TO8ROW_NEON) if (TestCpuFlag(kCpuHasNEON)) { Convert16To8Row = Convert16To8Row_Any_NEON; - if (IS_ALIGNED(width, 16)) { + if (IS_ALIGNED(halfwidth, 16)) { Convert16To8Row = Convert16To8Row_NEON; } } @@ -695,7 +695,7 @@ int I010ToNV12(const uint16_t* src_y, #if defined(HAS_CONVERT16TO8ROW_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { Convert16To8Row = Convert16To8Row_Any_SSSE3; - if (IS_ALIGNED(width, 16)) { + if (IS_ALIGNED(halfwidth, 16)) { Convert16To8Row = Convert16To8Row_SSSE3; } } @@ -703,7 +703,7 @@ int I010ToNV12(const uint16_t* src_y, #if defined(HAS_CONVERT16TO8ROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { Convert16To8Row = Convert16To8Row_Any_AVX2; - if (IS_ALIGNED(width, 32)) { + if (IS_ALIGNED(halfwidth, 32)) { Convert16To8Row = Convert16To8Row_AVX2; } } @@ -711,7 +711,7 @@ int I010ToNV12(const uint16_t* src_y, #if defined(HAS_CONVERT16TO8ROW_AVX512BW) if (TestCpuFlag(kCpuHasAVX512BW)) { Convert16To8Row = Convert16To8Row_Any_AVX512BW; - if (IS_ALIGNED(width, 64)) { + if (IS_ALIGNED(halfwidth, 64)) { Convert16To8Row = Convert16To8Row_AVX512BW; } } diff --git a/unit_test/convert_test.cc b/unit_test/convert_test.cc index a38e7fdf9..274559e73 100644 --- a/unit_test/convert_test.cc +++ b/unit_test/convert_test.cc @@ -12,6 +12,11 @@ #include #include +#if defined(__linux__) +#include +#include +#endif + #include "libyuv/basic_types.h" #include "libyuv/compare.h" #include "libyuv/convert.h" @@ -456,6 +461,80 @@ TESTPLANARTOBP(I210, uint16_t, 2, 2, 1, P210, uint16_t, 2, 2, 1, 10) TESTPLANARTOBP(I012, uint16_t, 2, 2, 2, P012, uint16_t, 2, 2, 2, 12) TESTPLANARTOBP(I212, uint16_t, 2, 2, 1, P212, uint16_t, 2, 2, 1, 12) +#if defined(__linux__) +// I010ToNV12 with a width whose chroma half-width is not a multiple of the +// widest SIMD register. The source chroma planes are placed against an +// inaccessible page so any access past the last row faults. +TEST_F(LibYUVConvertTest, I010ToNV12_HalfWidth) { + const int kWidth = 192; + const int kHeight = 2; + const int kHalfWidth = (kWidth + 1) / 2; + const int kHalfHeight = (kHeight + 1) / 2; + const long kPage = sysconf(_SC_PAGESIZE); + const int kUVSize = + kHalfWidth * kHalfHeight * static_cast(sizeof(uint16_t)); + ASSERT_LE(kUVSize, kPage); + + align_buffer_page_end(src_y, + kWidth * kHeight * static_cast(sizeof(uint16_t))); + align_buffer_page_end(dst_y_c, kWidth * kHeight); + align_buffer_page_end(dst_uv_c, kHalfWidth * kHalfHeight * 2); + align_buffer_page_end(dst_y_opt, kWidth * kHeight); + align_buffer_page_end(dst_uv_opt, kHalfWidth * kHalfHeight * 2); + uint8_t* src_u_mem = static_cast( + mmap(NULL, kPage * 2, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + uint8_t* src_v_mem = static_cast( + mmap(NULL, kPage * 2, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_NE(MAP_FAILED, src_u_mem); + ASSERT_NE(MAP_FAILED, src_v_mem); + ASSERT_EQ(0, mprotect(src_u_mem + kPage, kPage, PROT_NONE)); + ASSERT_EQ(0, mprotect(src_v_mem + kPage, kPage, PROT_NONE)); + uint16_t* src_y_p = reinterpret_cast(src_y); + uint16_t* src_u_p = reinterpret_cast(src_u_mem + kPage - kUVSize); + uint16_t* src_v_p = reinterpret_cast(src_v_mem + kPage - kUVSize); + + MemRandomize(src_y, kWidth * kHeight * static_cast(sizeof(uint16_t))); + MemRandomize(reinterpret_cast(src_u_p), kUVSize); + MemRandomize(reinterpret_cast(src_v_p), kUVSize); + for (int i = 0; i < kWidth * kHeight; ++i) { + src_y_p[i] &= 0x3ff; + } + for (int i = 0; i < kHalfWidth * kHalfHeight; ++i) { + src_u_p[i] &= 0x3ff; + src_v_p[i] &= 0x3ff; + } + memset(dst_y_c, 1, kWidth * kHeight); + memset(dst_uv_c, 2, kHalfWidth * kHalfHeight * 2); + memset(dst_y_opt, 101, kWidth * kHeight); + memset(dst_uv_opt, 102, kHalfWidth * kHalfHeight * 2); + + MaskCpuFlags(disable_cpu_flags_); + I010ToNV12(src_y_p, kWidth, src_u_p, kHalfWidth, src_v_p, kHalfWidth, dst_y_c, + kWidth, dst_uv_c, kHalfWidth * 2, kWidth, kHeight); + MaskCpuFlags(benchmark_cpu_info_); + for (int i = 0; i < benchmark_iterations_; ++i) { + I010ToNV12(src_y_p, kWidth, src_u_p, kHalfWidth, src_v_p, kHalfWidth, + dst_y_opt, kWidth, dst_uv_opt, kHalfWidth * 2, kWidth, kHeight); + } + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y_c[i], dst_y_opt[i]); + } + for (int i = 0; i < kHalfWidth * kHalfHeight * 2; ++i) { + ASSERT_EQ(dst_uv_c[i], dst_uv_opt[i]); + } + + munmap(src_u_mem, kPage * 2); + munmap(src_v_mem, kPage * 2); + free_aligned_buffer_page_end(src_y); + free_aligned_buffer_page_end(dst_y_c); + free_aligned_buffer_page_end(dst_uv_c); + free_aligned_buffer_page_end(dst_y_opt); + free_aligned_buffer_page_end(dst_uv_opt); +} +#endif // defined(__linux__) + #define TESTBPTOBPI(SRC_FMT_PLANAR, SRC_T, SRC_BPC, SRC_SUBSAMP_X, \ SRC_SUBSAMP_Y, FMT_PLANAR, DST_T, DST_BPC, DST_SUBSAMP_X, \ DST_SUBSAMP_Y, W1280, N, NEG, OFF, DOY, SRC_DEPTH, \