RISC-V support: misaligned-access fixes + scalar fast paths (fixes #28) - #29
RISC-V support: misaligned-access fixes + scalar fast paths (fixes #28)#29youkorr wants to merge 4 commits into
Conversation
Fixes the ESP32-P4 (RV32IMAFC) crash reported in tvlabs#28: on RISC-V a misaligned wide load/store TRAPS, unlike x86/ARM which tolerate it and unlike qemu-user which emulates it transparently. The High Profile 8x8 kernels (add_idct8x8, weighted DC in headers, intra) access the sample plane with aligned wide accesses while the plane is not guaranteed 16-aligned on this target, faulting after the first IDR. - edge264_internal.h: on __riscv, route loada32/64/128 (and the x2/x4 gathers) through fixed-size __builtin_memcpy wrappers. Values are identical; the compiler lowers them to trap-free narrow accesses. Other ISAs are unaffected (the wrappers are #ifdef __riscv). - edge264_residual.c, edge264_intra.c, edge264_headers.c: convert the remaining raw wide plane accesses to the loada/storea helpers so the RISC-V wrappers actually cover them. - edge264_slice.c: add a hard bound on the macroblock loop. If ctx->t is clobbered by a malformed live stream, pic_width_in_mbs can grow so the natural end-of-frame check never fires and the loop never returns (watchdog reset on embedded targets). The bound is captured while the context is still valid, with an absolute 65536-MB ceiling as a last resort; legitimate frames are never truncated. Validated bit-exact against the SSE build on 12 streams (IDR/P, CABAC+CAVLC, B-frames, weighted prediction, QP 15-40, multi-slice, deblock offsets, 8x8 transform on/off) with a qemu-riscv64 harness.
On an ISA without a vector unit (RISC-V without V, e.g. ESP32-P4) the compiler scalarizes the 128-bit deblocking code; the 16x16/8x8 byte transposes explode into thousands of byte moves per macroblock and dominate whole-frame cost: profiling the scalarized build shows deblock_mb at 62-75% of total decode time. - Add a scalar pixel pipeline (used on __riscv only): each edge is filtered in place with the exact ITU-T H.264 8.7.2.3/8.7.2.4 formulas, no transposes, with per-4-pixel-group (tC0<0) and per-pixel (filterSamplesFlag) early exits. The vector path is untouched and still used on SIMD targets. - Add two skip fast paths in deblock_mb, active on all targets: when every tC0 is -1 and no neighbour is intra, the pixel stage is a pure identity and is skipped; and a ~40-op scalar pre-test detects the all-bS=0 case for P macroblocks with uniform motion and no residuals (static surveillance scenes) before the vector strength computation. Measured on qemu-riscv64 (640x360 High/CABAC): IDR 102 -> 20 ms, static P-frames 11.7 -> 3.1 ms, moving P 29.7 -> 8.2 ms. On an ESP32-P4 @ 400 MHz the first IDR of a live RTSP camera went from ~13 s to ~180 ms. Bit-exact against the SSE build on the 12-stream suite.
The scalarized bilinear/6-tap interpolators are the second hotspot on SIMD-less RISC-V (up to 40% of P-frame time as soon as the scene moves, e.g. PTZ pans). Add scalar kernels implementing H.264 8.4.2.2.1/2 exactly, used on __riscv for unweighted single prediction only: - luma: 6-tap half-pel H/V, center j via unclipped intermediates (+512)>>10, quarter-pel averages for all 15 fractional positions - chroma: bilinear (8-xF)(8-yF) with pure-H/pure-V shortcuts, matching the interleaved Cb/Cr row layout of the vector kernel - full-pel unweighted prediction reduces to plain row copies (all targets): the dominant case on static surveillance streams (P_Skip) Weighted and bidirectional prediction keep the vector path (its weighting stage blends into dst). Measured on qemu-riscv64: moving scene P-frames 23.4 -> 7.2 ms. Bit-exact against the SSE build on the 12-stream suite, including the weightp and B-frame streams which exercise the vector fallback.
Requested in tvlabs#28: a short clip to guard the misaligned-access crash fix (commit 1), and a test file to guard the scalar deblock/motion fast paths (commits 2-3) against the planned Q4-2026 multithreading rework. - crash-repro-8x8-riscv.264: minimal High/CABAC/8x8-transform clip exercising the code path that used to trap on RISC-V. - riscv-scalar-fastpaths.264 + .golden: static + panning content exercising both fast paths, with per-frame FNV-1a hashes recorded from an unmodified x86 SIMD build as backend-independent ground truth. - riscv_regression_check.c: ~150-line, dependency-free (edge264.h + libc only) harness to decode a clip and diff per-frame hashes against a golden file, exiting non-zero with the first mismatching frame. See tests/README-riscv-regression.md for usage.
|
Hi Thibault, Pushed a 4th commit addressing both of your requests: Short clip for the crash fix — Test file for the scalar fast-path fix — To make it easy to run against any build (your default SIMD build, the scalar CLANG backend, a real RISC-V cross build, whatever), I included a small (~150-line) standalone harness, cc -O2 -I. -o riscv_regression_check tests/riscv_regression_check.c src/edge264.c -lpthread -lm
./riscv_regression_check tests/riscv-scalar-fastpaths.264 0 tests/riscv-scalar-fastpaths.goldenExits non-zero with the exact frame index and both hashes on any mismatch. I validated at I didn't touch Also opened #30 for an unrelated crash I hit while building this — not part of this PR. Cheers! |
RISC-V support: misaligned-access fixes + scalar fast paths (fixes #28)
Summary
This series makes edge264 work — and perform — on SIMD-less RISC-V
targets (developed and validated on the ESP32-P4, RV32IMAFC @ 400 MHz,
decoding live RTSP High Profile streams from surveillance cameras).
It contains three commits:
riscv: fix misaligned-trap crashes and harden the slice loop —
fixes the crash reported in ESP32‑P4 port: crash in add_idct8x8 when decoding High Profile streams #28. On RISC-V a misaligned wide access
traps (x86/ARM tolerate it, and qemu-user emulates it, which is why
it doesn't reproduce there). The aligned wide accesses of the High
Profile 8x8 kernels are routed through fixed-size
__builtin_memcpywrappers under
#ifdef __riscv— identical values, trap-freelowering, zero impact on other ISAs. Also adds a hard bound on the
slice macroblock loop so a clobbered
pic_width_in_mbscan no longerhang the decoder (watchdog reset on embedded targets).
riscv: scalar deblocking pipeline — with the vector code
scalarized by the compiler, the deblocking transposes dominate whole-
frame cost (62-75% of decode time in a gprof profile). Adds an
in-place scalar pixel pipeline (exact 8.7.2.3/8.7.2.4 formulas, used
on
__riscvonly) plus two cheap bS=0 skip fast paths.riscv: scalar unweighted motion compensation — same treatment
for the second hotspot: scalar 6-tap/bilinear kernels for the 15
fractional positions, full-pel unweighted prediction as plain row
copies. Weighted/bipred keep the vector path.
Correctness
Every commit is bit-exact against the SSE build, verified with a
decode-and-hash harness (x86 SIMD build as reference, riscv64 build run
under qemu-user) on 12 streams covering: IDR/P, CABAC and CAVLC,
B-frames, weighted prediction (weightp=2), QP 15 and 40, 8x8 transform
on/off, multi-slice frames, and non-zero deblock alpha/beta offsets.
Performance (640x360 High/CABAC, qemu-riscv64, same machine)
On the real target (ESP32-P4 @ 400 MHz, PSRAM frame buffers, live Tapo
camera): first IDR went from ~13 s to ~180 ms, P-frames from ~200 ms to
25-40 ms — real-time decoding of a 15 fps High Profile stream on a
microcontroller.
Non-RISC-V targets are unaffected: the scalar kernels and the memcpy
load wrappers are compiled only under
#ifdef __riscv; the two bS=0deblock skip paths are active everywhere but are pure wins.