[libyuv] Fix MSan uninitialized errors in AVX512 alpha and scale rows

Clang MemorySanitizer builds define LIBYUV_ENABLE_ROWWIN and compile
row_win.cc and scale_win.cc instead of inline assembly. Two AVX-512
intrinsic patterns in those files are not modeled by LLVM MSan:

1. _mm512_mask_cvtepi32_storeu_epi8 and _mm512_mask_cvtepi64_storeu_epi8
   lower to @llvm.x86.avx512.mask.pmov.db.mem.512 and
   @llvm.x86.avx512.mask.pmov.qb.mem.512, which LLVM MSan handles as
   unknown intrinsics without unpoisoning destination shadow memory.
   Replace them in ARGBExtractAlphaRow_AVX512BW, ScaleAddCols1_AVX512BW,
   and ScaleAddCols2_AVX512BW with _mm512_cvtepi32_epi8 /
   _mm512_cvtepi64_epi8 followed by _mm_mask_storeu_epi8, which lowers
   to @llvm.masked.store and still compiles to a single vpmovdb/vpmovqb
   instruction.

2. ARGBCopyYToAlphaRow_AVX512BW loaded dst and blended the alpha channel
   using _mm512_ternarylogic_epi32, which LLVM MSan does not model
   bitwise and flags when any destination byte (such as the old alpha
   channel) is uninitialized. Replace the load + vpternlogd + store in
   both row_win.cc and row_gcc.cc with byte-masked stores
   (_mm512_mask_storeu_epi8 / vmovdqu8 with mask 0x8888888888888888)
   that write only the alpha bytes without reading dst.

Expand ARGBCopyAlpha, ARGBExtractAlpha, and ARGBCopyYToAlpha unit tests
into _Any, _Unaligned, _Invert, and _Opt variants and poison destination
buffers under MSan to catch unmodeled masked stores. Document MSan
AVX-512 intrinsic guidelines in GEMINI.md.

Test: out/msan/libyuv_unittest --gtest_filter=*Alpha*:*ScalePlaneBox_Any*
Bug: 578615529
Change-Id: I54ac4a5844aca6cfe3aa3445fa04b8221c610b35
Reviewed-on: https://chromium-review.googlesource.com/c/libyuv/libyuv/+/8672001
Commit-Queue: Frank Barchard <fbarchard@google.com>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
This commit is contained in:
Frank Barchard 2026-10-27 14:29:31 -07:00 committed by libyuv-scoped@luci-project-accounts.iam.gserviceaccount.com
parent f506767c5f
commit a84bf45a21
6 changed files with 342 additions and 127 deletions

View File

@ -80,6 +80,28 @@ and compiler compatibility.
`"ymmN"` or `"zmmN"`, including for AVX2 and AVX512 code. Clang for `"ymmN"` or `"zmmN"`, including for AVX2 and AVX512 code. Clang for
Windows must preserve xmm6-xmm15 and does not save them when they are Windows must preserve xmm6-xmm15 and does not save them when they are
declared as ymm or zmm. 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 ### AArch64

View File

@ -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) { void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) {
uintptr_t temp; uintptr_t temp;
asm volatile( asm volatile(
"vpternlogd $0xff,%%zmm0,%%zmm0,%%zmm0 \n" "kmovq %4,%%k1 \n"
"vpsrld $0x8,%%zmm0,%%zmm0 \n" "sub $0x20,%2 \n"
"sub $0x20,%2 \n" "jl 2f \n"
"jl 2f \n"
LABELALIGN LABELALIGN
"1: \n" "1: \n"
"vpmovzxbd (%0),%%zmm1 \n" "vpmovzxbd (%0),%%zmm1 \n"
"vpmovzxbd 0x10(%0),%%zmm2 \n" "vpmovzxbd 0x10(%0),%%zmm2 \n"
"lea 0x20(%0),%0 \n" "lea 0x20(%0),%0 \n"
"vpslld $0x18,%%zmm1,%%zmm1 \n" "vpslld $0x18,%%zmm1,%%zmm1 \n"
"vpslld $0x18,%%zmm2,%%zmm2 \n" "vpslld $0x18,%%zmm2,%%zmm2 \n"
"vpternlogd $0xf8,(%1),%%zmm0,%%zmm1 \n" "vmovdqu8 %%zmm1,(%1)%{%%k1%} \n"
"vpternlogd $0xf8,0x40(%1),%%zmm0,%%zmm2 \n" "vmovdqu8 %%zmm2,0x40(%1)%{%%k1%} \n"
"vmovdqu32 %%zmm1,(%1) \n" "lea 0x80(%1),%1 \n"
"vmovdqu32 %%zmm2,0x40(%1) \n" "sub $0x20,%2 \n"
"lea 0x80(%1),%1 \n" "jge 1b \n"
"sub $0x20,%2 \n"
"jge 1b \n"
"2: \n" "2: \n"
"add $0x20,%2 \n" "add $0x20,%2 \n"
"jle 99f \n" "jle 99f \n"
"cmp $0x10,%2 \n" "cmp $0x10,%2 \n"
"jl 3f \n" "jl 3f \n"
"vpmovzxbd (%0),%%zmm1 \n" "vpmovzxbd (%0),%%zmm1 \n"
"lea 0x10(%0),%0 \n" "lea 0x10(%0),%0 \n"
"vpslld $0x18,%%zmm1,%%zmm1 \n" "vpslld $0x18,%%zmm1,%%zmm1 \n"
"vpternlogd $0xf8,(%1),%%zmm0,%%zmm1 \n" "vmovdqu8 %%zmm1,(%1)%{%%k1%} \n"
"vmovdqu32 %%zmm1,(%1) \n" "lea 0x40(%1),%1 \n"
"lea 0x40(%1),%1 \n" "sub $0x10,%2 \n"
"sub $0x10,%2 \n" "jz 99f \n"
"jz 99f \n"
"3: \n" "3: \n"
"mov $-1,%k3 \n" "mov $-1,%k3 \n"
"bzhi %k2,%k3,%k3 \n" "bzhi %k2,%k3,%k3 \n"
"kmovw %k3,%%k1 \n" "kmovw %k3,%%k1 \n"
"vpmovzxbd (%0),%%zmm1%{%%k1%}%{z%} \n" "vpmovzxbd (%0),%%zmm1%{%%k1%}%{z%} \n"
"vpslld $0x18,%%zmm1,%%zmm1 \n" "vpslld $0x18,%%zmm1,%%zmm1 \n"
"vpternlogd $0xf8,(%1),%%zmm0,%%zmm1%{%%k1%}%{z%} \n" "shl $0x2,%k2 \n"
"vmovdqu32 %%zmm1,(%1)%{%%k1%} \n" "bzhi %q2,%4,%3 \n"
"kmovq %3,%%k1 \n"
"vmovdqu8 %%zmm1,(%1)%{%%k1%} \n"
"99: \n" "99: \n"
"vzeroupper \n" "vzeroupper \n"
: "+r"(src), // %0 : "+r"(src), // %0
"+r"(dst), // %1 "+r"(dst), // %1
"+r"(width), // %2 "+r"(width), // %2
"=&r"(temp) // %3 "=&r"(temp) // %3
: : "r"(0x8888888888888888ull) // %4
: "memory", "cc", "xmm0", "xmm1", "xmm2", "k1"); : "memory", "cc", "xmm1", "xmm2", "k1");
} }
#endif // HAS_ARGBCOPYYTOALPHAROW_AVX512BW #endif // HAS_ARGBCOPYYTOALPHAROW_AVX512BW

View File

@ -3266,7 +3266,11 @@ void ARGBExtractAlphaRow_AVX512BW(const uint8_t* src_argb,
if (width > 0) { if (width > 0) {
__mmask16 mask = (__mmask16)((1u << width) - 1u); __mmask16 mask = (__mmask16)((1u << width) - 1u);
__m512i z0 = _mm512_maskz_loadu_epi32(mask, src_argb); __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(); _mm256_zeroupper();
} }
@ -3275,18 +3279,18 @@ void ARGBExtractAlphaRow_AVX512BW(const uint8_t* src_argb,
#ifdef HAS_ARGBCOPYYTOALPHAROW_AVX512BW #ifdef HAS_ARGBCOPYYTOALPHAROW_AVX512BW
LIBYUV_TARGET_AVX512BW LIBYUV_TARGET_AVX512BW
void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { 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) { while (width >= 32) {
__m512i a0 = _mm512_slli_epi32( __m512i a0 = _mm512_slli_epi32(
_mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24); _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24);
__m512i a1 = _mm512_slli_epi32( __m512i a1 = _mm512_slli_epi32(
_mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)(src + 16))), 24); _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)(src + 16))), 24);
a0 = _mm512_ternarylogic_epi32( _mm512_mask_storeu_epi8(dst, alpha_mask, a0);
a0, rgb_mask, _mm512_loadu_si512((const __m512i*)dst), 0xf8); _mm512_mask_storeu_epi8(dst + 64, alpha_mask, a1);
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);
src += 32; src += 32;
dst += 128; dst += 128;
width -= 32; width -= 32;
@ -3294,20 +3298,17 @@ void ARGBCopyYToAlphaRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) {
if (width >= 16) { if (width >= 16) {
__m512i a0 = _mm512_slli_epi32( __m512i a0 = _mm512_slli_epi32(
_mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24); _mm512_cvtepu8_epi32(_mm_loadu_si128((const __m128i*)src)), 24);
a0 = _mm512_ternarylogic_epi32( _mm512_mask_storeu_epi8(dst, alpha_mask, a0);
a0, rgb_mask, _mm512_loadu_si512((const __m512i*)dst), 0xf8);
_mm512_storeu_si512((__m512i*)dst, a0);
src += 16; src += 16;
dst += 64; dst += 64;
width -= 16; width -= 16;
} }
if (width > 0) { if (width > 0) {
__mmask16 mask = (__mmask16)((1u << width) - 1u); __mmask16 mask = (__mmask16)((1u << width) - 1u);
__mmask64 tail_alpha_mask = alpha_mask >> (64 - width * 4);
__m512i a0 = _mm512_slli_epi32( __m512i a0 = _mm512_slli_epi32(
_mm512_maskz_cvtepu8_epi32(mask, _mm_maskz_loadu_epi8(mask, src)), 24); _mm512_maskz_cvtepu8_epi32(mask, _mm_maskz_loadu_epi8(mask, src)), 24);
a0 = _mm512_ternarylogic_epi32(a0, rgb_mask, _mm512_mask_storeu_epi8(dst, tail_alpha_mask, a0);
_mm512_maskz_loadu_epi32(mask, dst), 0xf8);
_mm512_mask_storeu_epi32(dst, mask, a0);
} }
_mm256_zeroupper(); _mm256_zeroupper();
} }

View File

@ -1456,7 +1456,10 @@ void ScaleAddCols1_AVX512BW(int dst_width,
__m512i sum = _mm512_add_epi32(sum01, sum23); __m512i sum = _mm512_add_epi32(sum01, sum23);
sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16);
__mmask16 km = (__mmask16)((1U << dst_width) - 1); __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) { } else if (boxwidth <= 8) {
__m512i seq = __m512i seq =
@ -1507,7 +1510,10 @@ void ScaleAddCols1_AVX512BW(int dst_width,
sum = _mm512_add_epi32(sum, _mm512_srli_epi64(sum, 32)); sum = _mm512_add_epi32(sum, _mm512_srli_epi64(sum, 32));
sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vscale), 16);
__mmask8 km = (__mmask8)((1U << dst_width) - 1); __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 { } else {
int rem = boxwidth & 31; int rem = boxwidth & 31;
@ -1622,7 +1628,10 @@ void ScaleAddCols2_AVX512BW(int dst_width,
__m512i vsc = _mm512_mask_blend_epi32(k4, vscale0, vscale1); __m512i vsc = _mm512_mask_blend_epi32(k4, vscale0, vscale1);
sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16);
__mmask16 km = (__mmask16)((1U << dst_width) - 1); __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) { } else if (minboxwidth >= 4 && minboxwidth <= 7) {
__m512i seq = __m512i seq =
@ -1698,7 +1707,10 @@ void ScaleAddCols2_AVX512BW(int dst_width,
__m512i vsc = _mm512_mask_blend_epi64(k4, vscale0, vscale1); __m512i vsc = _mm512_mask_blend_epi64(k4, vscale0, vscale1);
sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16); sum = _mm512_srli_epi32(_mm512_mullo_epi32(sum, vsc), 16);
__mmask8 km = (__mmask8)((1U << dst_width) - 1); __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 { } else {
while (dst_width > 0) { while (dst_width > 0) {

View File

@ -29,6 +29,16 @@
#include "libyuv/row.h" /* For ScaleSumSamples_Neon */ #include "libyuv/row.h" /* For ScaleSumSamples_Neon */
#endif #endif
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#include <sanitizer/msan_interface.h>
#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 #define EXPECTED_UNATTENUATE_DIFF 0
namespace libyuv { namespace libyuv {
@ -2798,111 +2808,244 @@ TEST_F(LibYUVPlanarTest, TestARGBLumaColorTable) {
free_aligned_buffer_page_end(lumacolortable); free_aligned_buffer_page_end(lumacolortable);
} }
TEST_F(LibYUVPlanarTest, TestARGBCopyAlpha) { static int TestARGBCopyAlpha(int width,
const int kSize = benchmark_width_ * benchmark_height_ * 4; int height,
align_buffer_page_end(orig_pixels, kSize); 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_opt, kSize);
align_buffer_page_end(dst_pixels_c, kSize); align_buffer_page_end(dst_pixels_c, kSize);
MemRandomize(orig_pixels, kSize); MemRandomize(orig_pixels + off, kSize);
MemRandomize(dst_pixels_opt, kSize); MemRandomize(dst_pixels_c, kSize);
memcpy(dst_pixels_c, dst_pixels_opt, kSize); // Copy RGB bytes only, leaving alpha bytes in dst_pixels_opt uninitialized.
MSAN_POISON(dst_pixels_opt, kSize);
MaskCpuFlags(disable_cpu_flags_); for (int i = 0; i < width * height; ++i) {
ARGBCopyAlpha(orig_pixels, benchmark_width_ * 4, dst_pixels_c, dst_pixels_opt[i * 4 + 0] = dst_pixels_c[i * 4 + 0];
benchmark_width_ * 4, benchmark_width_, benchmark_height_); dst_pixels_opt[i * 4 + 1] = dst_pixels_c[i * 4 + 1];
MaskCpuFlags(benchmark_cpu_info_); dst_pixels_opt[i * 4 + 2] = dst_pixels_c[i * 4 + 2];
for (int i = 0; i < benchmark_iterations_; ++i) {
ARGBCopyAlpha(orig_pixels, benchmark_width_ * 4, dst_pixels_opt,
benchmark_width_ * 4, benchmark_width_, benchmark_height_);
} }
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) { for (int i = 0; i < kSize; ++i) {
ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); int abs_diff = abs(static_cast<int>(dst_pixels_c[i]) -
static_cast<int>(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_c);
free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(dst_pixels_opt);
free_aligned_buffer_page_end(orig_pixels); free_aligned_buffer_page_end(orig_pixels);
return max_diff;
} }
TEST_F(LibYUVPlanarTest, TestARGBExtractAlpha) { TEST_F(LibYUVPlanarTest, ARGBCopyAlpha_Any) {
const int kPixels = benchmark_width_ * benchmark_height_; int max_diff = TestARGBCopyAlpha(benchmark_width_ - 1, benchmark_height_,
align_buffer_page_end(src_pixels, kPixels * 4); 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_opt, kPixels);
align_buffer_page_end(dst_pixels_c, kPixels); align_buffer_page_end(dst_pixels_c, kPixels);
MemRandomize(src_pixels, kPixels * 4); MemRandomize(src_pixels + off, kPixels * 4);
MemRandomize(dst_pixels_opt, kPixels); // Leave dst_pixels_opt uninitialized to verify MSan unpoisons all written
memcpy(dst_pixels_c, dst_pixels_opt, kPixels); // alpha bytes (including masked tail stores).
MSAN_POISON(dst_pixels_opt, kPixels);
MaskCpuFlags(disable_cpu_flags_); MaskCpuFlags(disable_cpu_flags);
ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_c, ARGBExtractAlpha(src_pixels + off, width * 4, dst_pixels_c, width, width,
benchmark_width_, benchmark_width_, benchmark_height_); invert * 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(benchmark_cpu_info_); MaskCpuFlags(benchmark_cpu_info);
ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_opt, for (int i = 0; i < benchmark_iterations; ++i) {
benchmark_width_, benchmark_width_, benchmark_height_); ARGBExtractAlpha(src_pixels + off, width * 4, dst_pixels_opt, width, width,
double opt_time = get_time(); invert * height);
for (int i = 0; i < benchmark_iterations_; ++i) {
ARGBExtractAlpha(src_pixels, benchmark_width_ * 4, dst_pixels_opt,
benchmark_width_, benchmark_width_, benchmark_height_);
} }
opt_time = (get_time() - opt_time) / benchmark_iterations_; int max_diff = 0;
// Report performance of C vs OPT
printf("%8d us C - %8d us OPT\n", static_cast<int>(c_time * 1e6),
static_cast<int>(opt_time * 1e6));
for (int i = 0; i < kPixels; ++i) { for (int i = 0; i < kPixels; ++i) {
ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); int abs_diff = abs(static_cast<int>(dst_pixels_c[i]) -
static_cast<int>(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_c);
free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(dst_pixels_opt);
free_aligned_buffer_page_end(src_pixels); free_aligned_buffer_page_end(src_pixels);
return max_diff;
} }
TEST_F(LibYUVPlanarTest, TestARGBCopyYToAlpha) { TEST_F(LibYUVPlanarTest, ARGBExtractAlpha_Any) {
const int kPixels = benchmark_width_ * benchmark_height_; int max_diff = TestARGBExtractAlpha(benchmark_width_ - 1, benchmark_height_,
align_buffer_page_end(orig_pixels, kPixels); 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_opt, kPixels * 4);
align_buffer_page_end(dst_pixels_c, 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(orig_pixels + off, kPixels);
MemRandomize(dst_pixels_opt, kPixels * 4); MemRandomize(dst_pixels_c, kPixels * 4);
memcpy(dst_pixels_c, dst_pixels_opt, kPixels * 4); // Copy RGB bytes only, leaving alpha bytes in dst_pixels_opt uninitialized.
MSAN_POISON(dst_pixels_opt, kPixels * 4);
MaskCpuFlags(disable_cpu_flags_); for (int i = 0; i < kPixels; ++i) {
ARGBCopyYToAlpha(orig_pixels, benchmark_width_, dst_pixels_c, dst_pixels_opt[i * 4 + 0] = dst_pixels_c[i * 4 + 0];
benchmark_width_ * 4, benchmark_width_, benchmark_height_); dst_pixels_opt[i * 4 + 1] = dst_pixels_c[i * 4 + 1];
double c_time = get_time(); dst_pixels_opt[i * 4 + 2] = dst_pixels_c[i * 4 + 2];
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_);
} }
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 MaskCpuFlags(disable_cpu_flags);
printf("%8d us C - %8d us OPT\n", static_cast<int>(c_time * 1e6), ARGBCopyYToAlpha(orig_pixels + off, width, dst_pixels_c, width * 4, width,
static_cast<int>(opt_time * 1e6)); 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) { for (int i = 0; i < kPixels * 4; ++i) {
ASSERT_EQ(dst_pixels_c[i], dst_pixels_opt[i]); int abs_diff = abs(static_cast<int>(dst_pixels_c[i]) -
static_cast<int>(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<int>(dst_pixels_c[i * 4 + 3]) -
static_cast<int>(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_c);
free_aligned_buffer_page_end(dst_pixels_opt); free_aligned_buffer_page_end(dst_pixels_opt);
free_aligned_buffer_page_end(orig_pixels); 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, static int TestARGBRect(int width,

View File

@ -25,6 +25,16 @@
#include "libyuv/scale_row.h" // For ScaleRowDown2Box_Odd_C #include "libyuv/scale_row.h" // For ScaleRowDown2Box_Odd_C
#endif #endif
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#include <sanitizer/msan_interface.h>
#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 STRINGIZE(line) #line
#define FILELINESTR(file, line) file ":" STRINGIZE(line) #define FILELINESTR(file, line) file ":" STRINGIZE(line)
@ -517,6 +527,35 @@ TEST_F(LibYUVScaleTest, PlaneTestRotate_Box) {
free_aligned_buffer_page_end(orig_pixels); 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) { TEST_F(LibYUVScaleTest, PlaneTest1_Box) {
align_buffer_page_end(orig_pixels, 3); align_buffer_page_end(orig_pixels, 3);
align_buffer_page_end(dst_pixels, 3); align_buffer_page_end(dst_pixels, 3);