diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/Dockerfile b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/Dockerfile index 9c9f3030..d896cf0a 100644 --- a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/Dockerfile +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/Dockerfile @@ -32,6 +32,7 @@ RUN sh /tmp/ebv-setup/build_ffmpeg.sh work && rm -rf /tmp/ebv-setup COPY environment/workspace/ /home/workspace/ COPY environment/prepare_media.py /root/ebv-prepare_media.py +COPY environment/check_baseline_padding.py /root/ebv-check-baseline-padding.py RUN set -eux; \ mkdir -p /home/workspace/swscale-impl /home/workspace/media; \ ln -sfn /home/workspace /app; \ @@ -39,6 +40,9 @@ RUN set -eux; \ gcc -shared -fPIC -O2 -o /home/workspace/libswscale_public_baseline.so /home/workspace/baseline_wrapper.c \ -I/usr/local/include -Wl,--whole-archive /usr/local/lib/libswscale.a -Wl,--no-whole-archive \ /usr/local/lib/libavutil.a -lm -lpthread; \ + FFMPEG_BASELINE_SO=/home/workspace/libswscale_public_baseline.so \ + PYTHONPATH=/home/workspace python3 /root/ebv-check-baseline-padding.py; \ + rm -f /root/ebv-check-baseline-padding.py; \ python3 /root/ebv-prepare_media.py; \ rm -f /root/ebv-prepare_media.py; \ cp -a /home/workspace/media /home/workspace/swscale-impl/media; \ diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/check_baseline_padding.py b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/check_baseline_padding.py new file mode 100644 index 00000000..72dccc93 --- /dev/null +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/check_baseline_padding.py @@ -0,0 +1,160 @@ +"""Real FFmpeg regression. Set FFMPEG_BASELINE_SO to the compiled task shim. + +Optionally set FFMPEG_PREVIOUS_BASELINE_SO for byte-for-byte legacy parity. +Run with python3 environment/check_baseline_padding.py from the task directory. +""" +import ctypes +import json +import mmap +import os +from pathlib import Path +import random +import sys +import unittest + +WORKSPACE = Path(__file__).resolve().parent / "workspace" +sys.path.insert(0, str(WORKSPACE)) +from pixel_formats import allocate_image, image_to_bytes, load_swscale_library + +BYTE_POINTER = ctypes.POINTER(ctypes.c_uint8) +BASELINE = os.environ.get("FFMPEG_BASELINE_SO") +PREVIOUS = os.environ.get("FFMPEG_PREVIOUS_BASELINE_SO") + + +def convert(library, width, height, fmt, padding, color, negative_stride=False): + """Keep only logical source pixels readable before a guard page.""" + bpp = 3 if fmt in (5, 6) else 4 + row_bytes = width * bpp + stride = row_bytes + padding + size = stride * height + page = mmap.PAGESIZE + readable = ((size + page - 1) // page) * page + storage = mmap.mmap(-1, readable + page, prot=mmap.PROT_READ | mmap.PROT_WRITE) + address = ctypes.addressof(ctypes.c_uint8.from_buffer(storage)) + libc = ctypes.CDLL(None, use_errno=True) + libc.mprotect.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int] + libc.mprotect.restype = ctypes.c_int + if libc.mprotect(address + readable, page, 0): + raise OSError(ctypes.get_errno(), "mprotect") + first_row = address + readable - size + rng = random.Random(width * 8191 + height * 97 + fmt) + for y in range(height): + data = bytes(rng.randrange(256) for _ in range(row_bytes)) + offset = first_row + y * stride + ctypes.memmove(offset, data, len(data)) + if padding: + tail = (color * ((padding + len(color) - 1) // len(color)))[:padding] + ctypes.memmove(offset + row_bytes, tail, padding) + src = (BYTE_POINTER * 4)(ctypes.cast(first_row, BYTE_POINTER)) + src_stride = (ctypes.c_int * 4)(stride) + if negative_stride: + src[0] = ctypes.cast(first_row + (height - 1) * stride, BYTE_POINTER) + src_stride[0] = -stride + dst, dst_stride, buffers = allocate_image(1, width, height) + ctx = library.swscale_create(width, height, fmt, width, height, 1, 1) + if not ctx: + raise RuntimeError("swscale_create failed") + try: + code = library.swscale_process(ctx, src, src_stride, dst, dst_stride) + if code: + raise RuntimeError(f"swscale_process returned {code}") + return [plane.hex() for plane in image_to_bytes(1, width, height, dst, dst_stride)] + finally: + library.swscale_destroy(ctx) + + +def isolated_conversion(library, *args, **kwargs): + """A native fault becomes an assertion result instead of killing the suite.""" + import resource + + rd, wr = os.pipe() + child = os.fork() + if child == 0: + os.close(rd) + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + try: + value = {"planes": convert(library, *args, **kwargs)} + except Exception as exc: + value = {"error": str(exc)} + os.write(wr, json.dumps(value).encode()) + os._exit(0) + os.close(wr) + with os.fdopen(rd, "rb") as pipe: + payload = pipe.read() + _, status = os.waitpid(child, 0) + if os.WIFSIGNALED(status): + return {"signal": os.WTERMSIG(status)} + if os.WEXITSTATUS(status): + return {"exit_code": os.WEXITSTATUS(status)} + return json.loads(payload) + + +@unittest.skipUnless(BASELINE and hasattr(os, "fork"), "requires FFMPEG_BASELINE_SO and POSIX") +class ReferencePaddingTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.library = load_swscale_library(BASELINE) + cls.previous = load_swscale_library(PREVIOUS) if PREVIOUS else None + + def assert_success(self, result): + self.assertIn("planes", result, result) + return result["planes"] + + def test_tight_odd_inputs_and_padding_independence(self): + for fmt in (5, 6, 7, 8): + for width in (3, 7, 17, 129): + for height in (1, 3, 7): + with self.subTest(fmt=fmt, width=width, height=height): + expected = self.assert_success(isolated_conversion( + self.library, width, height, fmt, 64, b"\0" + )) + for padding, color in ((0, b"\0"), (32, b"\xff\0\0"), (32, b"\0\0\xff")): + actual = self.assert_success(isolated_conversion( + self.library, width, height, fmt, padding, color + )) + self.assertEqual(actual, expected) + + def test_negative_stride(self): + for fmt in (5, 6, 7, 8): + with self.subTest(fmt=fmt): + expected = self.assert_success(isolated_conversion( + self.library, 17, 3, fmt, 64, b"\0", negative_stride=True + )) + actual = self.assert_success(isolated_conversion( + self.library, 17, 3, fmt, 0, b"\0", negative_stride=True + )) + self.assertEqual(actual, expected) + + def test_legacy_even_width_parity(self): + if not self.previous: + self.skipTest("requires FFMPEG_PREVIOUS_BASELINE_SO") + for fmt in (5, 6, 7, 8): + for width in (4, 8, 18, 130): + for height in (1, 3): + with self.subTest(fmt=fmt, width=width, height=height): + before = self.assert_success(isolated_conversion( + self.previous, width, height, fmt, 64, b"\0" + )) + after = self.assert_success(isolated_conversion( + self.library, width, height, fmt, 64, b"\0" + )) + self.assertEqual(after, before) + + def test_legacy_zero_padded_odd_parity(self): + if not self.previous: + self.skipTest("requires FFMPEG_PREVIOUS_BASELINE_SO") + for fmt in (5, 6, 7, 8): + for width in (3, 7, 17, 129): + for height in (1, 3): + with self.subTest(fmt=fmt, width=width, height=height): + before = self.assert_success(isolated_conversion( + self.previous, width, height, fmt, 64, b"\0" + )) + after = self.assert_success(isolated_conversion( + self.library, width, height, fmt, 64, b"\0" + )) + self.assertEqual(after, before) + + +if __name__ == "__main__": + unittest.main() diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/baseline_wrapper.c b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/baseline_wrapper.c index 68ff00cb..3e039003 100644 --- a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/baseline_wrapper.c +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/baseline_wrapper.c @@ -13,9 +13,13 @@ #include #include +#include +#include +#include #include #include #include +#include /* ── Format mapping (SWS_PIXFMT_* → AVPixelFormat) ─────────────────────── */ @@ -50,6 +54,9 @@ typedef struct { struct SwsContext *sws; int src_h; int dst_h; + int src_row_bytes; + uint8_t *padded_src[4]; + int padded_stride[4]; } BaselineCtx; /* ── Exported API ──────────────────────────────────────────────────────── */ @@ -61,6 +68,9 @@ void *swscale_create(int src_w, int src_h, int src_fmt, enum AVPixelFormat av_dst = map_pixfmt(dst_fmt); if (av_src == AV_PIX_FMT_NONE || av_dst == AV_PIX_FMT_NONE) return NULL; + if (src_w <= 0 || src_w == INT_MAX || src_h <= 0 || + dst_w <= 0 || dst_h <= 0) + return NULL; struct SwsContext *sws = sws_getContext( src_w, src_h, av_src, @@ -69,7 +79,7 @@ void *swscale_create(int src_w, int src_h, int src_fmt, if (!sws) return NULL; - BaselineCtx *ctx = malloc(sizeof(BaselineCtx)); + BaselineCtx *ctx = calloc(1, sizeof(BaselineCtx)); if (!ctx) { sws_freeContext(sws); return NULL; @@ -77,6 +87,24 @@ void *swscale_create(int src_w, int src_h, int src_fmt, ctx->sws = sws; ctx->src_h = src_h; ctx->dst_h = dst_h; + /* Scalar packed-RGB chroma conversion reads pairs, including a partner + * beyond odd-width rows. Supply a zero-valued partner from owned storage. + * Allocate once per context; common even-width inputs keep the direct path. + */ + if ((src_w & 1) && + (av_src == AV_PIX_FMT_RGB24 || av_src == AV_PIX_FMT_BGR24 || + av_src == AV_PIX_FMT_RGBA || av_src == AV_PIX_FMT_BGRA)) { + ctx->src_row_bytes = av_image_get_linesize(av_src, src_w, 0); + int size = av_image_alloc(ctx->padded_src, ctx->padded_stride, + src_w + 1, src_h, av_src, 32); + if (ctx->src_row_bytes < 0 || size < 0) { + av_freep(&ctx->padded_src[0]); + sws_freeContext(sws); + free(ctx); + return NULL; + } + memset(ctx->padded_src[0], 0, size); + } return ctx; } @@ -88,6 +116,19 @@ int swscale_process(void *opaque, BaselineCtx *ctx = (BaselineCtx *)opaque; if (!ctx || !ctx->sws) return -1; + if (ctx->padded_src[0]) { + if (!src_data || !src_stride || !src_data[0] || + (src_stride[0] > -ctx->src_row_bytes && + src_stride[0] < ctx->src_row_bytes)) + return -1; + for (int y = 0; y < ctx->src_h; y++) { + memcpy(ctx->padded_src[0] + (ptrdiff_t)y * ctx->padded_stride[0], + src_data[0] + (ptrdiff_t)y * src_stride[0], + ctx->src_row_bytes); + } + src_data = (const uint8_t *const *)ctx->padded_src; + src_stride = ctx->padded_stride; + } int ret = sws_scale(ctx->sws, src_data, src_stride, 0, ctx->src_h, @@ -100,5 +141,6 @@ void swscale_destroy(void *opaque) { if (!ctx) return; if (ctx->sws) sws_freeContext(ctx->sws); + av_freep(&ctx->padded_src[0]); free(ctx); } diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/swscale_api.h b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/swscale_api.h index a8541818..c397aa3e 100644 --- a/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/swscale_api.h +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/environment/workspace/swscale_api.h @@ -13,6 +13,8 @@ * * linesize[i] gives the byte stride of plane i (may be larger than the * minimum row size due to alignment padding). + * For odd-width packed RGB, the scalar baseline completes the last chroma + * pair with a zero-valued pixel, independently of source alignment padding. */ #ifndef SWSCALE_API_H diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/judge/Dockerfile b/edgebench/tasks/ffmpeg_swscale_reimplementation/judge/Dockerfile index ca172c62..20449bd8 100644 --- a/edgebench/tasks/ffmpeg_swscale_reimplementation/judge/Dockerfile +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/judge/Dockerfile @@ -31,6 +31,7 @@ COPY environment/workspace/pixel_formats.py environment/workspace/baseline_wrapp environment/workspace/run_dev_bench.py /opt/tests/ COPY environment/workspace/pixel_formats.py environment/workspace/baseline_wrapper.c /judge/ COPY judge/run.sh judge/eval.sh judge/grade.py /judge/ +COPY environment/check_baseline_padding.py /root/ebv-check-baseline-padding.py RUN set -eux; \ chmod +x /opt/tests/*.sh; \ chmod 755 /judge/run.sh /judge/eval.sh; \ @@ -39,6 +40,9 @@ RUN set -eux; \ gcc -shared -fPIC -O2 -o /verifier-data/libswscale_baseline.so /judge/baseline_wrapper.c \ -I/usr/local/include -Wl,--whole-archive /usr/local/lib/libswscale.a -Wl,--no-whole-archive \ /usr/local/lib/libavutil.a -lm -lpthread; \ + FFMPEG_BASELINE_SO=/verifier-data/libswscale_baseline.so \ + PYTHONPATH=/judge python3 /root/ebv-check-baseline-padding.py; \ + rm -f /root/ebv-check-baseline-padding.py; \ cp /verifier-data/libswscale_baseline.so /home/workspace/libswscale_public_baseline.so; \ sha256sum /verifier-data/libswscale_baseline.so | awk '{print $1}' > /verifier-data/baseline_hash.txt diff --git a/edgebench/tasks/ffmpeg_swscale_reimplementation/task.toml b/edgebench/tasks/ffmpeg_swscale_reimplementation/task.toml index cf690f08..b42e1be6 100644 --- a/edgebench/tasks/ffmpeg_swscale_reimplementation/task.toml +++ b/edgebench/tasks/ffmpeg_swscale_reimplementation/task.toml @@ -4,7 +4,7 @@ name = "Ffmpeg Swscale Reimplementation" category = "Systems & Software Engineering" language = "C++" # as in v1 tasks.jsonl; the deliverable is Rust (or Zig), see NOTES.md description = "Reimplement FFmpeg's libswscale pixel-format conversion and scaling library in Rust, handling multiple pixel formats and scaling algorithms. A correctness-passing scaffold is provided; the agent must optimize for speed via SIMD." -version = "1.0.0" +version = "1.1.0" status = "migrated" [environment]