From 2f4a8fce4336d63f74a2e21fcf0302aaefac46e9 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Fri, 24 Jul 2026 06:55:34 +0000 Subject: [PATCH 18/38] avcodec/ppc: make emulated_edge_mc stores byte-precise on dst The store_full16()/store_partial() helpers spliced payload into memory with a read-modify-write of the two straddled aligned 16-byte words, touching up to ~30 bytes past the last payload byte of a row. That is fine mid-buffer, but emulated_edge_mc writes into exact-size scratch buffers, and an audit of all callers found three with insufficient trailing slack for the last row: vp3's VP4 loop-filter MC uses a stack buffer loop[12*12] with zero slack, vp8's edge_emu_buffer[21*32] has only 11 bytes of slack for a worst-case 21x21 block, and checkasm's own dst[64*64] has zero slack (svq3/vp3-MC/dirac are additionally unsafe for small linesizes). The splice would read and write out of bounds. Replace the splice with byte-precise spans: scalar bytes up to the first 16-byte boundary, plain aligned vec_st for the bulk, scalar bytes for the tail. Nothing outside [dst, dst+n) is read or written, and the bulk store no longer pays the 2-load/2-store RMW cost. src-side reads keep the standard unaligned_load pattern (<= 15 bytes overread into reference-frame slack). Validated under qemu -cpu 7400: new standalone guard-byte test over exact-size destination buffers (12x12/ls12, 21x21/ls32, 64x64/ls64 and odd sizes, all clamp corners) shows bit-exact payload vs the C reference with all guard bytes intact; checkasm videodsp passes across 3 seeds; full suite 530/530. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011BUPvhbJmFUU7zpuZf36JZ --- libavcodec/ppc/videodsp.c | 86 ++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/libavcodec/ppc/videodsp.c b/libavcodec/ppc/videodsp.c index 35bc51e..c58ad9c 100644 --- a/libavcodec/ppc/videodsp.c +++ b/libavcodec/ppc/videodsp.c @@ -49,72 +49,66 @@ static void prefetch_ppc(const uint8_t *mem, ptrdiff_t stride, int h) * - splat_run(): fills a row span with a single replicated edge byte * (the hot inner loop the C code does one byte at a time). * - * Every store is width-precise: it writes exactly the requested number of - * bytes and never touches the neighbouring span. This matters because the - * three horizontal regions (left edge / copied middle / right edge) are - * adjacent in the same row and hold different data, and because checkasm's - * dst is only 16-aligned at the row start -- once buf += start_x the working - * pointer is arbitrarily aligned. So a plain vec_st (which truncates its - * address down to the 16-byte boundary and always writes a full 16 bytes) - * cannot be used directly; store_full16()/store_partial() do a read-modify- - * write splice that writes only the requested bytes at an unaligned address. + * Every store is byte-precise on dst: scalar bytes up to the first 16-byte + * boundary, aligned vec_st for the bulk, scalar bytes for the tail. Nothing + * outside [dst, dst+n) is read or written. This matters because the three + * horizontal regions of a row (left edge / copied middle / right edge) are + * adjacent and hold different data, and -- more importantly -- because + * several callers pass exact-size scratch buffers with no trailing slack at + * all (vp3's stack loop[12*12], vp8's edge_emu_buffer[21*32], checkasm's + * dst[64*64]): any read-modify-write vector splice at the end of the last + * row would touch out-of-bounds memory. + * + * src-side reads go through unaligned_load(), which can read up to 15 bytes + * past the last payload byte of a 16-byte chunk -- the standard accepted + * overread pattern here (src is a reference frame plane; those have >= 16 + * bytes of allocation slack precisely for SIMD overreads). The scalar tail + * copy reads exactly the remaining bytes. */ -/* Splice the 16 logical bytes of v into the two aligned words at dst, - * preserving every byte outside [dst, dst+16). Works for any dst alignment. - * Same read-modify-write splice as unaligned_store16() in vp8dsp_altivec.c. */ -static av_always_inline void store_full16(vec_u8 v, uint8_t *dst) -{ - vec_u8 lo = vec_ld(0, dst); - vec_u8 hi = vec_ld(15, dst); - vec_u8 edges = vec_perm(hi, lo, vec_lvsl(0, dst)); /* undo the load rotate */ - vec_u8 align = vec_lvsr(0, dst); - hi = vec_perm(v, edges, align); - lo = vec_perm(edges, v, align); - vec_st(hi, 15, dst); - vec_st(lo, 0, dst); -} - -/* store the low n (1..15) bytes of v at dst, leaving all other bytes of the - * two straddled 16-byte words untouched. Works for any dst alignment. */ -static av_always_inline void store_partial(vec_u8 v, uint8_t *dst, int n) -{ - /* length mask, in dst's logical byte order: lane i = 0xff if i < n. */ - const vec_u8 counter = - (const vec_u8){0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; - const vec_u8 nv = vec_splat((vec_u8){ (uint8_t)n,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0 }, 0); - const vec_u8 lenmask = (vec_u8)vec_cmplt(counter, nv); - - /* merge payload over current contents so lanes >= n keep memory's value */ - vec_u8 cur = unaligned_load(0, dst); - store_full16(vec_sel(cur, v, lenmask), dst); -} - /* copy n bytes from src to dst (vectorized memcpy), arbitrary alignment. */ static av_always_inline void store_run(uint8_t *dst, const uint8_t *src, int n) { + int head = (int)(-(uintptr_t)dst & 15); + + if (head) { + head = FFMIN(head, n); + for (int i = 0; i < head; i++) + dst[i] = src[i]; + dst += head; + src += head; + n -= head; + } while (n >= 16) { - store_full16(unaligned_load(0, src), dst); + vec_st(unaligned_load(0, src), 0, dst); src += 16; dst += 16; n -= 16; } - if (n > 0) - store_partial(unaligned_load(0, src), dst, n); + for (int i = 0; i < n; i++) + dst[i] = src[i]; } /* fill n bytes at dst with the byte value b (edge replication). */ static av_always_inline void splat_run(uint8_t *dst, uint8_t b, int n) { vec_u8 v = vec_splat((vec_u8){ b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, 0); + int head = (int)(-(uintptr_t)dst & 15); + + if (head) { + head = FFMIN(head, n); + for (int i = 0; i < head; i++) + dst[i] = b; + dst += head; + n -= head; + } while (n >= 16) { - store_full16(v, dst); + vec_st(v, 0, dst); dst += 16; n -= 16; } - if (n > 0) - store_partial(v, dst, n); + for (int i = 0; i < n; i++) + dst[i] = b; } static void emulated_edge_mc_altivec(uint8_t *buf, const uint8_t *src, -- 2.43.0