diff --git a/GEMINI.md b/GEMINI.md index 9ef92b254..a82327f5b 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -80,6 +80,28 @@ and compiler compatibility. `"ymmN"` or `"zmmN"`, including for AVX2 and AVX512 code. Clang for Windows must preserve xmm6-xmm15 and does not save them when they are declared as ymm or zmm. +4. **MSan Intrinsics (`row_win.cc` / `scale_win.cc`)**: Clang builds with + MemorySanitizer (`-fsanitize=memory`) define `LIBYUV_ENABLE_ROWWIN` and + compile `row_win.cc` and `scale_win.cc` instead of inline assembly. Avoid + AVX-512 intrinsics that LLVM's MSan pass does not instrument: + * **Down-converting masked stores**: Never use + `_mm512_mask_cvtepi32_storeu_epi8`, `_mm512_mask_cvtepi64_storeu_epi8`, + `_mm512_mask_cvtepi16_storeu_epi8`, or + `_mm512_mask_cvtepi32_storeu_epi16`. They lower to target-specific + `@llvm.x86.avx512.mask.pmov.*.mem.*` intrinsics that LLVM MSan does not + model, leaving the destination shadow memory poisoned (uninitialized). + Instead, convert to a narrower vector first and use a standard masked + store (e.g. `_mm_mask_storeu_epi8(dst, mask, _mm512_cvtepi32_epi8(v))`), + which lowers to `@llvm.masked.store` for MSan and still compiles to a + single `vpmovdb` instruction at `-O2`. + * **Ternary logic (`_mm512_ternarylogic_epi32` / `epi64`) on partially + uninitialized vectors**: LLVM MSan does not model `vpternlog` truth + tables bitwise and flags `use-of-uninitialized-value` if any bit of any + of the three inputs is uninitialized, even if masked off by `imm8`. For + partial channel updates (e.g. writing alpha into `dst` in + `ARGBCopyYToAlphaRow_AVX512BW`), use byte-masked stores + (`_mm512_mask_storeu_epi8`) instead of reading `dst` and blending with + `_mm512_ternarylogic_epi32`. ### AArch64 diff --git a/source/row_gcc.cc b/source/row_gcc.cc index 3296dad51..40f990dc0 100644 --- a/source/row_gcc.cc +++ b/source/row_gcc.cc @@ -8825,57 +8825,55 @@ void ARGBCopyYToAlphaRow_AVX2(const uint8_t* src, uint8_t* dst, int width) { void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { uintptr_t temp; asm volatile( - "vpternlogd $0xff,%%zmm0,%%zmm0,%%zmm0 \n" - "vpsrld $0x8,%%zmm0,%%zmm0 \n" - "sub $0x20,%2 \n" - "jl 2f \n" + "kmovq %4,%%k1 \n" + "sub $0x20,%2 \n" + "jl 2f \n" LABELALIGN "1: \n" - "vpmovzxbd (%0),%%zmm1 \n" - "vpmovzxbd 0x10(%0),%%zmm2 \n" - "lea 0x20(%0),%0 \n" - "vpslld $0x18,%%zmm1,%%zmm1 \n" - "vpslld $0x18,%%zmm2,%%zmm2 \n" - "vpternlogd $0xf8,(%1),%%zmm0,%%zmm1 \n" - "vpternlogd $0xf8,0x40(%1),%%zmm0,%%zmm2 \n" - "vmovdqu32 %%zmm1,(%1) \n" - "vmovdqu32 %%zmm2,0x40(%1) \n" - "lea 0x80(%1),%1 \n" - "sub $0x20,%2 \n" - "jge 1b \n" + "vpmovzxbd (%0),%%zmm1 \n" + "vpmovzxbd 0x10(%0),%%zmm2 \n" + "lea 0x20(%0),%0 \n" + "vpslld $0x18,%%zmm1,%%zmm1 \n" + "vpslld $0x18,%%zmm2,%%zmm2 \n" + "vmovdqu8 %%zmm1,(%1)%{%%k1%} \n" + "vmovdqu8 %%zmm2,0x40(%1)%{%%k1%} \n" + "lea 0x80(%1),%1 \n" + "sub $0x20,%2 \n" + "jge 1b \n" "2: \n" - "add $0x20,%2 \n" - "jle 99f \n" - "cmp $0x10,%2 \n" - "jl 3f \n" - "vpmovzxbd (%0),%%zmm1 \n" - "lea 0x10(%0),%0 \n" - "vpslld $0x18,%%zmm1,%%zmm1 \n" - "vpternlogd $0xf8,(%1),%%zmm0,%%zmm1 \n" - "vmovdqu32 %%zmm1,(%1) \n" - "lea 0x40(%1),%1 \n" - "sub $0x10,%2 \n" - "jz 99f \n" + "add $0x20,%2 \n" + "jle 99f \n" + "cmp $0x10,%2 \n" + "jl 3f \n" + "vpmovzxbd (%0),%%zmm1 \n" + "lea 0x10(%0),%0 \n" + "vpslld $0x18,%%zmm1,%%zmm1 \n" + "vmovdqu8 %%zmm1,(%1)%{%%k1%} \n" + "lea 0x40(%1),%1 \n" + "sub $0x10,%2 \n" + "jz 99f \n" "3: \n" - "mov $-1,%k3 \n" - "bzhi %k2,%k3,%k3 \n" - "kmovw %k3,%%k1 \n" - "vpmovzxbd (%0),%%zmm1%{%%k1%}%{z%} \n" - "vpslld $0x18,%%zmm1,%%zmm1 \n" - "vpternlogd $0xf8,(%1),%%zmm0,%%zmm1%{%%k1%}%{z%} \n" - "vmovdqu32 %%zmm1,(%1)%{%%k1%} \n" + "mov $-1,%k3 \n" + "bzhi %k2,%k3,%k3 \n" + "kmovw %k3,%%k1 \n" + "vpmovzxbd (%0),%%zmm1%{%%k1%}%{z%} \n" + "vpslld $0x18,%%zmm1,%%zmm1 \n" + "shl $0x2,%k2 \n" + "bzhi %q2,%4,%3 \n" + "kmovq %3,%%k1 \n" + "vmovdqu8 %%zmm1,(%1)%{%%k1%} \n" "99: \n" "vzeroupper \n" - : "+r"(src), // %0 - "+r"(dst), // %1 - "+r"(width), // %2 - "=&r"(temp) // %3 - : - : "memory", "cc", "xmm0", "xmm1", "xmm2", "k1"); + : "+r"(src), // %0 + "+r"(dst), // %1 + "+r"(width), // %2 + "=&r"(temp) // %3 + : "r"(0x8888888888888888ull) // %4 + : "memory", "cc", "xmm1", "xmm2", "k1"); } #endif // HAS_ARGBCOPYYTOALPHAROW_AVX512BW diff --git a/source/row_win.cc b/source/row_win.cc index 6a08ebbab..da55bd12e 100644 --- a/source/row_win.cc +++ b/source/row_win.cc @@ -3266,7 +3266,11 @@ void ARGBExtractAlphaRow_AVX512BW(const uint8_t* src_argb, if (width > 0) { __mmask16 mask = (__mmask16)((1u << width) - 1u); __m512i z0 = _mm512_maskz_loadu_epi32(mask, src_argb); - _mm512_mask_cvtepi32_storeu_epi8(dst_a, mask, _mm512_srli_epi32(z0, 24)); + __m128i a0 = _mm512_cvtepi32_epi8(_mm512_srli_epi32(z0, 24)); + // Use _mm_mask_storeu_epi8 instead of _mm512_mask_cvtepi32_storeu_epi8 + // because LLVM MSan does not instrument @llvm.x86.avx512.mask.pmov.db.mem + // to unpoison destination shadow memory. + _mm_mask_storeu_epi8(dst_a, mask, a0); } _mm256_zeroupper(); } @@ -3275,18 +3279,18 @@ void ARGBExtractAlphaRow_AVX512BW(const uint8_t* src_argb, #ifdef HAS_ARGBCOPYYTOALPHAROW_AVX512BW LIBYUV_TARGET_AVX512BW void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { - __m512i rgb_mask = _mm512_set1_epi32(0x00ffffff); + // Store only the alpha byte (every 4th byte) using a byte mask instead of + // loading dst and blending with _mm512_ternarylogic_epi32. This avoids + // reading uninitialized destination bytes and avoids + // @llvm.x86.avx512.pternlog which LLVM MSan does not model bitwise. + const __mmask64 alpha_mask = 0x8888888888888888ull; while (width >= 32) { __m512i a0 = _mm512_slli_epi32( _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24); __m512i a1 = _mm512_slli_epi32( _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)(src + 16))), 24); - a0 = _mm512_ternarylogic_epi32( - a0, rgb_mask, _mm512_loadu_si512((const __m512i*)dst), 0xf8); - a1 = _mm512_ternarylogic_epi32( - a1, rgb_mask, _mm512_loadu_si512((const __m512i*)(dst + 64)), 0xf8); - _mm512_storeu_si512((__m512i*)dst, a0); - _mm512_storeu_si512((__m512i*)(dst + 64), a1); + _mm512_mask_storeu_epi8(dst, alpha_mask, a0); + _mm512_mask_storeu_epi8(dst + 64, alpha_mask, a1); src += 32; dst += 128; width -= 32; @@ -3294,20 +3298,17 @@ void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { if (width >= 16) { __m512i a0 = _mm512_slli_epi32( _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24); - a0 = _mm512_ternarylogic_epi32( - a0, rgb_mask, _mm512_loadu_si512((const __m512i*)dst), 0xf8); - _mm512_storeu_si512((__m512i*)dst, a0); + _mm512_mask_storeu_epi8(dst, alpha_mask, a0); src += 16; dst += 64; width -= 16; } if (width > 0) { __mmask16 mask = (__mmask16)((1u << width) - 1u); + __mmask64 tail_alpha_mask = alpha_mask >> (64 - width * 4); __m512i a0 = _mm512_slli_epi32( _mm512_maskz_cvtepu8_epi32(mask, _mm_maskz_loadu_epi8(mask, src)), 24); - a0 = _mm512_ternarylogic_epi32(a0, rgb_mask, - _mm512_maskz_loadu_epi32(mask, dst), 0xf8); - _mm512_mask_storeu_epi32(dst, mask, a0); + _mm512_mask_storeu_epi8(dst, tail_alpha_mask, a0); } _mm256_zeroupper(); } diff --git a/source/scale_win.cc b/source/scale_win.cc index a85cb0a4f..f2ad7f078 100644 --- a/source/scale_win.cc +++ b/source/scale_win.cc @@ -1456,7 +1456,10 @@ void ScaleAddCols1_AVX512BW(int dst_width, __m512i sum = _mm512_add_epi32(sum01, sum23); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16); __mmask16 km = (__mmask16)((1U << dst_width) - 1); - _mm512_mask_cvtepi32_storeu_epi8(dst_ptr, km, sum); + // Use _mm_mask_storeu_epi8 instead of _mm512_mask_cvtepi32_storeu_epi8 + // because LLVM MSan does not instrument @llvm.x86.avx512.mask.pmov.db.mem + // to unpoison destination shadow memory. + _mm_mask_storeu_epi8(dst_ptr, km, _mm512_cvtepi32_epi8(sum)); } } else if (boxwidth <= 8) { __m512i seq = @@ -1507,7 +1510,10 @@ void ScaleAddCols1_AVX512BW(int dst_width, sum = _mm512_add_epi32(sum, _mm512_srli_epi64(sum, 32)); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16); __mmask8 km = (__mmask8)((1U << dst_width) - 1); - _mm512_mask_cvtepi64_storeu_epi8(dst_ptr, km, sum); + // Use _mm_mask_storeu_epi8 instead of _mm512_mask_cvtepi64_storeu_epi8 + // because LLVM MSan does not instrument @llvm.x86.avx512.mask.pmov.qb.mem + // to unpoison destination shadow memory. + _mm_mask_storeu_epi8(dst_ptr, km, _mm512_cvtepi64_epi8(sum)); } } else { int rem = boxwidth & 31; @@ -1622,7 +1628,10 @@ void ScaleAddCols2_AVX512BW(int dst_width, __m512i vsc = _mm512_mask_blend_epi32(k4, vscale0, vscale1); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16); __mmask16 km = (__mmask16)((1U << dst_width) - 1); - _mm512_mask_cvtepi32_storeu_epi8(dst_ptr, km, sum); + // Use _mm_mask_storeu_epi8 instead of _mm512_mask_cvtepi32_storeu_epi8 + // because LLVM MSan does not instrument @llvm.x86.avx512.mask.pmov.db.mem + // to unpoison destination shadow memory. + _mm_mask_storeu_epi8(dst_ptr, km, _mm512_cvtepi32_epi8(sum)); } } else if (minboxwidth >= 4 && minboxwidth <= 7) { __m512i seq = @@ -1698,7 +1707,10 @@ void ScaleAddCols2_AVX512BW(int dst_width, __m512i vsc = _mm512_mask_blend_epi64(k4, vscale0, vscale1); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16); __mmask8 km = (__mmask8)((1U << dst_width) - 1); - _mm512_mask_cvtepi64_storeu_epi8(dst_ptr, km, sum); + // Use _mm_mask_storeu_epi8 instead of _mm512_mask_cvtepi64_storeu_epi8 + // because LLVM MSan does not instrument @llvm.x86.avx512.mask.pmov.qb.mem + // to unpoison destination shadow memory. + _mm_mask_storeu_epi8(dst_ptr, km, _mm512_cvtepi64_epi8(sum)); } } else { while (dst_width > 0) { diff --git a/unit_test/planar_test.cc b/unit_test/planar_test.cc index d12be099b..5d7e14490 100644 --- a/unit_test/planar_test.cc +++ b/unit_test/planar_test.cc @@ -29,6 +29,16 @@ #include "libyuv/row.h" /* For ScaleSumSamples_Neon */ #endif +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#include +#define MSAN_POISON(ptr, size) __msan_allocated_memory(ptr, size) +#endif +#endif +#ifndef MSAN_POISON +#define MSAN_POISON(ptr, size) +#endif + #define EXPECTED_UNATTENUATE_DIFF 0 namespace libyuv { @@ -2798,111 +2808,244 @@ TEST_F(LibYUVPlanarTest, TestARGBLumaColorTable) { free_aligned_buffer_page_end(lumacolortable); } -TEST_F(LibYUVPlanarTest, TestARGBCopyAlpha) { - const int kSize = benchmark_width_ * benchmark_height_ * 4; - align_buffer_page_end(orig_pixels, kSize); +static int TestARGBCopyAlpha(int width, + int height, + int benchmark_iterations, + int disable_cpu_flags, + int benchmark_cpu_info, + int invert, + int off) { + if (width < 1) { + width = 1; + } + const int kStride = width * 4; + const int kSize = kStride * height; + align_buffer_page_end(orig_pixels, kSize + off); align_buffer_page_end(dst_pixels_opt, kSize); align_buffer_page_end(dst_pixels_c, kSize); - MemRandomize(orig_pixels, kSize); - MemRandomize(dst_pixels_opt, kSize); - memcpy(dst_pixels_c, dst_pixels_opt, kSize); - - MaskCpuFlags(disable_cpu_flags_); - ARGBCopyAlpha(orig_pixels, benchmark_width_ * 4, dst_pixels_c, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); - MaskCpuFlags(benchmark_cpu_info_); - - for (int i = 0; i < benchmark_iterations_; ++i) { - ARGBCopyAlpha(orig_pixels, benchmark_width_ * 4, dst_pixels_opt, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); + MemRandomize(orig_pixels + off, kSize); + MemRandomize(dst_pixels_c, kSize); + // Copy RGB bytes only, leaving alpha bytes in dst_pixels_opt uninitialized. + MSAN_POISON(dst_pixels_opt, kSize); + for (int i = 0; i < width * height; ++i) { + dst_pixels_opt[i * 4 + 0] = dst_pixels_c[i * 4 + 0]; + dst_pixels_opt[i * 4 + 1] = dst_pixels_c[i * 4 + 1]; + dst_pixels_opt[i * 4 + 2] = dst_pixels_c[i * 4 + 2]; } + + MaskCpuFlags(disable_cpu_flags); + ARGBCopyAlpha(orig_pixels + off, kStride, dst_pixels_c, kStride, width, + invert * height); + MaskCpuFlags(benchmark_cpu_info); + + for (int i = 0; i < benchmark_iterations; ++i) { + ARGBCopyAlpha(orig_pixels + off, kStride, dst_pixels_opt, kStride, width, + invert * height); + } + int max_diff = 0; for (int i = 0; i < kSize; ++i) { - ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); + int abs_diff = abs(static_cast(dst_pixels_c[i]) - + static_cast(dst_pixels_opt[i])); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } } free_aligned_buffer_page_end(dst_pixels_c); free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(orig_pixels); + return max_diff; } -TEST_F(LibYUVPlanarTest, TestARGBExtractAlpha) { - const int kPixels = benchmark_width_ * benchmark_height_; - align_buffer_page_end(src_pixels, kPixels * 4); +TEST_F(LibYUVPlanarTest, ARGBCopyAlpha_Any) { + int max_diff = TestARGBCopyAlpha(benchmark_width_ - 1, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyAlpha_Unaligned) { + int max_diff = TestARGBCopyAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 1); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyAlpha_Invert) { + int max_diff = TestARGBCopyAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, -1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyAlpha_Opt) { + int max_diff = TestARGBCopyAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); +} + +static int TestARGBExtractAlpha(int width, + int height, + int benchmark_iterations, + int disable_cpu_flags, + int benchmark_cpu_info, + int invert, + int off) { + if (width < 1) { + width = 1; + } + const int kPixels = width * height; + align_buffer_page_end(src_pixels, kPixels * 4 + off); align_buffer_page_end(dst_pixels_opt, kPixels); align_buffer_page_end(dst_pixels_c, kPixels); - MemRandomize(src_pixels, kPixels * 4); - MemRandomize(dst_pixels_opt, kPixels); - memcpy(dst_pixels_c, dst_pixels_opt, kPixels); + MemRandomize(src_pixels + off, kPixels * 4); + // Leave dst_pixels_opt uninitialized to verify MSan unpoisons all written + // alpha bytes (including masked tail stores). + MSAN_POISON(dst_pixels_opt, kPixels); - MaskCpuFlags(disable_cpu_flags_); - ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_c, - benchmark_width_, benchmark_width_, benchmark_height_); - double c_time = get_time(); - ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_c, - benchmark_width_, benchmark_width_, benchmark_height_); - c_time = (get_time() - c_time); + MaskCpuFlags(disable_cpu_flags); + ARGBExtractAlpha(src_pixels + off, width * 4, dst_pixels_c, width, width, + invert * height); - MaskCpuFlags(benchmark_cpu_info_); - ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_opt, - benchmark_width_, benchmark_width_, benchmark_height_); - double opt_time = get_time(); - for (int i = 0; i < benchmark_iterations_; ++i) { - ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_opt, - benchmark_width_, benchmark_width_, benchmark_height_); + MaskCpuFlags(benchmark_cpu_info); + for (int i = 0; i < benchmark_iterations; ++i) { + ARGBExtractAlpha(src_pixels + off, width * 4, dst_pixels_opt, width, width, + invert * height); } - opt_time = (get_time() - opt_time) / benchmark_iterations_; - // Report performance of C vs OPT - printf("%8d us C - %8d us OPT\n", static_cast(c_time * 1e6), - static_cast(opt_time * 1e6)); + int max_diff = 0; for (int i = 0; i < kPixels; ++i) { - ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); + int abs_diff = abs(static_cast(dst_pixels_c[i]) - + static_cast(dst_pixels_opt[i])); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } } free_aligned_buffer_page_end(dst_pixels_c); free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(src_pixels); + return max_diff; } -TEST_F(LibYUVPlanarTest, TestARGBCopyYToAlpha) { - const int kPixels = benchmark_width_ * benchmark_height_; - align_buffer_page_end(orig_pixels, kPixels); +TEST_F(LibYUVPlanarTest, ARGBExtractAlpha_Any) { + int max_diff = TestARGBExtractAlpha(benchmark_width_ - 1, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBExtractAlpha_Unaligned) { + int max_diff = TestARGBExtractAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 1); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBExtractAlpha_Invert) { + int max_diff = TestARGBExtractAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, -1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBExtractAlpha_Opt) { + int max_diff = TestARGBExtractAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); +} + +static int TestARGBCopyYToAlpha(int width, + int height, + int benchmark_iterations, + int disable_cpu_flags, + int benchmark_cpu_info, + int invert, + int off) { + if (width < 1) { + width = 1; + } + const int kPixels = width * height; + align_buffer_page_end(orig_pixels, kPixels + off); align_buffer_page_end(dst_pixels_opt, kPixels * 4); align_buffer_page_end(dst_pixels_c, kPixels * 4); + align_buffer_page_end(dst_uninit_opt, kPixels * 4); - MemRandomize(orig_pixels, kPixels); - MemRandomize(dst_pixels_opt, kPixels * 4); - memcpy(dst_pixels_c, dst_pixels_opt, kPixels * 4); - - MaskCpuFlags(disable_cpu_flags_); - ARGBCopyYToAlpha(orig_pixels, benchmark_width_, dst_pixels_c, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); - double c_time = get_time(); - ARGBCopyYToAlpha(orig_pixels, benchmark_width_, dst_pixels_c, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); - c_time = (get_time() - c_time); - - MaskCpuFlags(benchmark_cpu_info_); - ARGBCopyYToAlpha(orig_pixels, benchmark_width_, dst_pixels_opt, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); - double opt_time = get_time(); - for (int i = 0; i < benchmark_iterations_; ++i) { - ARGBCopyYToAlpha(orig_pixels, benchmark_width_, dst_pixels_opt, - benchmark_width_ * 4, benchmark_width_, benchmark_height_); + MemRandomize(orig_pixels + off, kPixels); + MemRandomize(dst_pixels_c, kPixels * 4); + // Copy RGB bytes only, leaving alpha bytes in dst_pixels_opt uninitialized. + MSAN_POISON(dst_pixels_opt, kPixels * 4); + for (int i = 0; i < kPixels; ++i) { + dst_pixels_opt[i * 4 + 0] = dst_pixels_c[i * 4 + 0]; + dst_pixels_opt[i * 4 + 1] = dst_pixels_c[i * 4 + 1]; + dst_pixels_opt[i * 4 + 2] = dst_pixels_c[i * 4 + 2]; } - opt_time = (get_time() - opt_time) / benchmark_iterations_; + // Also test with completely uninitialized destination buffer for MSan. + MSAN_POISON(dst_uninit_opt, kPixels * 4); - // Report performance of C vs OPT - printf("%8d us C - %8d us OPT\n", static_cast(c_time * 1e6), - static_cast(opt_time * 1e6)); + MaskCpuFlags(disable_cpu_flags); + ARGBCopyYToAlpha(orig_pixels + off, width, dst_pixels_c, width * 4, width, + invert * height); + + MaskCpuFlags(benchmark_cpu_info); + ARGBCopyYToAlpha(orig_pixels + off, width, dst_uninit_opt, width * 4, width, + invert * height); + for (int i = 0; i < benchmark_iterations; ++i) { + ARGBCopyYToAlpha(orig_pixels + off, width, dst_pixels_opt, width * 4, width, + invert * height); + } + int max_diff = 0; for (int i = 0; i < kPixels * 4; ++i) { - ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); + int abs_diff = abs(static_cast(dst_pixels_c[i]) - + static_cast(dst_pixels_opt[i])); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } + } + for (int i = 0; i < kPixels; ++i) { + int abs_diff = abs(static_cast(dst_pixels_c[i * 4 + 3]) - + static_cast(dst_uninit_opt[i * 4 + 3])); + if (abs_diff > max_diff) { + max_diff = abs_diff; + } } + free_aligned_buffer_page_end(dst_uninit_opt); free_aligned_buffer_page_end(dst_pixels_c); free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(orig_pixels); + return max_diff; +} + +TEST_F(LibYUVPlanarTest, ARGBCopyYToAlpha_Any) { + int max_diff = TestARGBCopyYToAlpha(benchmark_width_ - 1, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyYToAlpha_Unaligned) { + int max_diff = TestARGBCopyYToAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 1); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyYToAlpha_Invert) { + int max_diff = TestARGBCopyYToAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, -1, 0); + ASSERT_EQ(0, max_diff); +} + +TEST_F(LibYUVPlanarTest, ARGBCopyYToAlpha_Opt) { + int max_diff = TestARGBCopyYToAlpha(benchmark_width_, benchmark_height_, + benchmark_iterations_, disable_cpu_flags_, + benchmark_cpu_info_, +1, 0); + ASSERT_EQ(0, max_diff); } static int TestARGBRect(int width, diff --git a/unit_test/scale_plane_test.cc b/unit_test/scale_plane_test.cc index 544a0f098..bf2ec6b8a 100644 --- a/unit_test/scale_plane_test.cc +++ b/unit_test/scale_plane_test.cc @@ -25,6 +25,16 @@ #include "libyuv/scale_row.h" // For ScaleRowDown2Box_Odd_C #endif +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#include +#define MSAN_POISON(ptr, size) __msan_allocated_memory(ptr, size) +#endif +#endif +#ifndef MSAN_POISON +#define MSAN_POISON(ptr, size) +#endif + #define STRINGIZE(line) #line #define FILELINESTR(file, line) file ":" STRINGIZE(line) @@ -517,6 +527,35 @@ TEST_F(LibYUVScaleTest, PlaneTestRotate_Box) { free_aligned_buffer_page_end(orig_pixels); } +TEST_F(LibYUVScaleTest, ScalePlaneBox_Any) { + const int kDstW = 29; + const int kDstH = 3; + const int kSrcH = 9; + for (int src_w : {87, 174, 73, 155}) { + const int kSrcSize = src_w * kSrcH; + const int kDstSize = kDstW * kDstH; + align_buffer_page_end(src_pixels, kSrcSize); + align_buffer_page_end(dst_c_pixels, kDstSize); + align_buffer_page_end(dst_opt_pixels, kDstSize); + MemRandomize(src_pixels, kSrcSize); + MSAN_POISON(dst_opt_pixels, kDstSize); + + MaskCpuFlags(disable_cpu_flags_); + ScalePlane(src_pixels, src_w, src_w, kSrcH, dst_c_pixels, kDstW, kDstW, + kDstH, kFilterBox); + MaskCpuFlags(benchmark_cpu_info_); + ScalePlane(src_pixels, src_w, src_w, kSrcH, dst_opt_pixels, kDstW, kDstW, + kDstH, kFilterBox); + + for (int i = 0; i < kDstSize; ++i) { + ASSERT_EQ(dst_c_pixels[i], dst_opt_pixels[i]); + } + free_aligned_buffer_page_end(dst_opt_pixels); + free_aligned_buffer_page_end(dst_c_pixels); + free_aligned_buffer_page_end(src_pixels); + } +} + TEST_F(LibYUVScaleTest, PlaneTest1_Box) { align_buffer_page_end(orig_pixels, 3); align_buffer_page_end(dst_pixels, 3);