* [FFmpeg-devel] [PATCH] avcodec/wasm: WebAssembly-friendly scalar CABAC fast path optimization (PR #21202)
@ 2025-12-15 8:53 xiatao via ffmpeg-devel
0 siblings, 0 replies; only message in thread
From: xiatao via ffmpeg-devel @ 2025-12-15 8:53 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: xiatao
PR #21202 opened by xiatao
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21202
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21202.patch
Signed-off-by: Xia Tao <xiatao@gmail.com>
Rewrite branchless bit-twiddling into wasm-friendly logic: use comparisons to produce 0/1, then turn that into 0/-1 masks (or a tiny explicit branch), reducing tricky shifts and long dependency chains.
Fewer memory accesses: cache c->range / c->low and CABAC table base pointers in locals to avoid repeated loads/stores.
Shorter critical path: keep the decode step as a straight-line sequence (threshold compare → update low/range → update state) that JITs optimize well.
Note: it is important to emphasize that this optimization relies purely on standard scalar instructions and makes no use of WebAssembly SIMD128 features.
Benchmark:
wasmtime tests/checkasm/checkasm --bench=cab*
benchmarking with native FFmpeg timers
nop: 43.9
checkasm: using random seed 786092358
checkasm: bench runs 1024 (1 << 10)
SIMD128:
- cabac.cabac [OK]
- hevc_idct.idct [OK]
- hevc_sao.sao_band [OK]
- hevc_sao.sao_edge [OK]
checkasm: all 21 tests passed
cabac.bypass_c: 7.2 ( 1.00x)
cabac.bypass_simd128: 3.0 ( 2.37x)
cabac.bypass_sign_c: 3.6 ( 1.00x)
cabac.bypass_sign_simd128: 2.8 ( 1.28x)
cabac.get_c: 9.2 ( 1.00x)
cabac.get_simd128: 7.8 ( 1.17x)
From bf014423ed67f56827f82f100154d2b93421fbc0 Mon Sep 17 00:00:00 2001
From: Xia Tao <xiatao@gmail.com>
Date: Mon, 15 Dec 2025 16:25:47 +0800
Subject: [PATCH] avcodec/wasm: WebAssembly-friendly scalar CABAC fast path
optimization
Signed-off-by: Xia Tao <xiatao@gmail.com>
---
libavcodec/hevc/cabac.c | 7 ++
libavcodec/wasm/Makefile | 1 +
libavcodec/wasm/cabac.c | 130 ++++++++++++++++++++++++
libavcodec/wasm/cabac.h | 32 ++++++
tests/checkasm/Makefile | 1 +
tests/checkasm/cabac.c | 201 ++++++++++++++++++++++++++++++++++++++
tests/checkasm/checkasm.c | 3 +
tests/checkasm/checkasm.h | 1 +
8 files changed, 376 insertions(+)
create mode 100644 libavcodec/wasm/Makefile
create mode 100644 libavcodec/wasm/cabac.c
create mode 100644 libavcodec/wasm/cabac.h
create mode 100644 tests/checkasm/cabac.c
diff --git a/libavcodec/hevc/cabac.c b/libavcodec/hevc/cabac.c
index 55d5741f87..c0fe23ec2e 100644
--- a/libavcodec/hevc/cabac.c
+++ b/libavcodec/hevc/cabac.c
@@ -29,6 +29,13 @@
#include "hevc.h"
#include "hevcdec.h"
+#if ARCH_WASM
+#include "libavcodec/wasm/cabac.h"
+#define get_cabac ff_get_cabac_wasm
+#define get_cabac_bypass ff_get_cabac_bypass_wasm
+#define get_cabac_bypass_sign ff_get_cabac_bypass_sign_wasm
+#endif
+
#define CABAC_MAX_BIN 31
// ELEM(NAME, NUM_BINS)
diff --git a/libavcodec/wasm/Makefile b/libavcodec/wasm/Makefile
new file mode 100644
index 0000000000..e0a5066197
--- /dev/null
+++ b/libavcodec/wasm/Makefile
@@ -0,0 +1 @@
+OBJS-$(CONFIG_CABAC) += wasm/cabac.o
diff --git a/libavcodec/wasm/cabac.c b/libavcodec/wasm/cabac.c
new file mode 100644
index 0000000000..f5ffbb53ed
--- /dev/null
+++ b/libavcodec/wasm/cabac.c
@@ -0,0 +1,130 @@
+/*
+ * CABAC helpers for WebAssembly
+ *
+ * Copyright (c) 2025 Xia Tao
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "libavcodec/wasm/cabac.h"
+#include "libavcodec/cabac_functions.h"
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+
+#ifndef UNCHECKED_BITSTREAM_READER
+#define UNCHECKED_BITSTREAM_READER !CONFIG_SAFE_BITSTREAM_READER
+#endif
+
+static av_always_inline int get_cabac_core_wasm(CABACContext *c, uint8_t *state)
+{
+ int s = *state;
+ int range = c->range;
+ int low = c->low;
+ const uint8_t *lps_range = ff_h264_cabac_tables + H264_LPS_RANGE_OFFSET;
+ const uint8_t *mlps_state = ff_h264_cabac_tables + H264_MLPS_STATE_OFFSET;
+ const uint8_t *norm_shift = ff_h264_cabac_tables + H264_NORM_SHIFT_OFFSET;
+ int RangeLPS = lps_range[2 * (range & 0xC0) + s];
+ int lps_mask;
+ int bit;
+
+ {
+ int range_mps = range - RangeLPS;
+ int thresh = range_mps << (CABAC_BITS + 1);
+ int is_lps = low > thresh;
+
+ lps_mask = -is_lps;
+
+ low = low - (thresh & lps_mask);
+ range = range_mps + ((RangeLPS - range_mps) & lps_mask);
+ }
+
+ s ^= lps_mask;
+ *state = (mlps_state + 128)[s];
+ bit = s & 1;
+
+ lps_mask = norm_shift[range];
+ range <<= lps_mask;
+ low <<= lps_mask;
+
+ c->range = range;
+ c->low = low;
+
+ if (!(low & CABAC_MASK))
+ refill2(c);
+
+ return bit;
+}
+
+int ff_get_cabac_wasm(CABACContext *c, uint8_t *state)
+{
+ return get_cabac_core_wasm(c, state);
+}
+
+static av_always_inline int get_cabac_bypass_core_wasm(CABACContext *c)
+{
+ c->low += c->low;
+
+ if (!(c->low & CABAC_MASK))
+ refill(c);
+
+ {
+ int range_shifted = c->range << (CABAC_BITS + 1);
+ int low = c->low;
+ int bit = low >= range_shifted;
+
+ if (bit)
+ low -= range_shifted;
+
+ c->low = low;
+ return bit;
+ }
+}
+
+int ff_get_cabac_bypass_wasm(CABACContext *c)
+{
+ return get_cabac_bypass_core_wasm(c);
+}
+
+static av_always_inline int get_cabac_bypass_sign_core_wasm(CABACContext *c, int val)
+{
+ c->low += c->low;
+
+ if (!(c->low & CABAC_MASK))
+ refill(c);
+
+ {
+ int range_shifted = c->range << (CABAC_BITS + 1);
+ int low = c->low;
+ int bit = low >= range_shifted;
+
+ if (bit)
+ low -= range_shifted;
+
+ c->low = low;
+
+ return bit ? val : -val;
+ }
+}
+
+int ff_get_cabac_bypass_sign_wasm(CABACContext *c, int val)
+{
+ return get_cabac_bypass_sign_core_wasm(c, val);
+}
diff --git a/libavcodec/wasm/cabac.h b/libavcodec/wasm/cabac.h
new file mode 100644
index 0000000000..73935850f2
--- /dev/null
+++ b/libavcodec/wasm/cabac.h
@@ -0,0 +1,32 @@
+/*
+ * CABAC helpers for WebAssembly
+ *
+ * Copyright (c) 2025 Xia Tao
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_WASM_CABAC_H
+#define AVCODEC_WASM_CABAC_H
+
+#include "libavcodec/cabac.h"
+
+int ff_get_cabac_wasm(CABACContext *c, uint8_t *state);
+int ff_get_cabac_bypass_wasm(CABACContext *c);
+int ff_get_cabac_bypass_sign_wasm(CABACContext *c, int val);
+
+#endif /* AVCODEC_WASM_CABAC_H */
diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index 9e675ce189..2874657080 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -12,6 +12,7 @@ AVCODECOBJS-$(CONFIG_H264CHROMA) += h264chroma.o
AVCODECOBJS-$(CONFIG_H264DSP) += h264dsp.o
AVCODECOBJS-$(CONFIG_H264PRED) += h264pred.o
AVCODECOBJS-$(CONFIG_H264QPEL) += h264qpel.o
+AVCODECOBJS-$(CONFIG_CABAC) += cabac.o
AVCODECOBJS-$(CONFIG_HPELDSP) += hpeldsp.o
AVCODECOBJS-$(CONFIG_IDCTDSP) += idctdsp.o
AVCODECOBJS-$(CONFIG_LLAUDDSP) += llauddsp.o
diff --git a/tests/checkasm/cabac.c b/tests/checkasm/cabac.c
new file mode 100644
index 0000000000..1cd45061fb
--- /dev/null
+++ b/tests/checkasm/cabac.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2025 Xia Tao
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+
+#include "libavcodec/cabac.h"
+#include "libavcodec/cabac_functions.h"
+
+#include "checkasm.h"
+
+#if ARCH_WASM
+
+#include "libavcodec/wasm/cabac.h"
+
+#define CABAC_BUF_SIZE 8192
+#define CABAC_STATE_SIZE 64
+#define CABAC_BIN_COUNT 48
+
+static int get_cabac_c(CABACContext *c, uint8_t *state)
+{
+ return get_cabac_inline(c, state);
+}
+
+static int get_cabac_bypass_c(CABACContext *c)
+{
+ return get_cabac_bypass(c);
+}
+
+static int get_cabac_bypass_sign_c(CABACContext *c, int val)
+{
+ return get_cabac_bypass_sign(c, val);
+}
+
+static void fill_random(uint8_t *buf, int size)
+{
+ for (int i = 0; i < size; i++)
+ buf[i] = rnd();
+}
+
+static void init_states(uint8_t *dst, int size)
+{
+ for (int i = 0; i < size; i++)
+ dst[i] = rnd();
+}
+
+static void setup_contexts(CABACContext *c0, CABACContext *c1, uint8_t *buf, int buf_size)
+{
+ if (ff_init_cabac_decoder(c0, buf, buf_size) < 0)
+ fail();
+ *c1 = *c0;
+}
+
+static void check_get_cabac(int use_wasm)
+{
+ uint8_t buf[CABAC_BUF_SIZE];
+ uint8_t state_ref[CABAC_STATE_SIZE];
+ uint8_t state_new[CABAC_STATE_SIZE];
+ CABACContext c_ref, c_new;
+ declare_func(int, CABACContext *, uint8_t *);
+ func_type *func = use_wasm ?
+#if ARCH_WASM
+ ff_get_cabac_wasm
+#else
+ get_cabac_c
+#endif
+ : get_cabac_c;
+
+ fill_random(buf, sizeof(buf));
+ init_states(state_ref, CABAC_STATE_SIZE);
+ memcpy(state_new, state_ref, sizeof(state_ref));
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+
+ if (check_func(func, "cabac.get")) {
+ for (int i = 0; i < CABAC_BIN_COUNT; i++) {
+ int idx = i % CABAC_STATE_SIZE;
+ int ret_ref = call_ref(&c_ref, &state_ref[idx]);
+ int ret_new = call_new(&c_new, &state_new[idx]);
+
+ if (ret_ref != ret_new ||
+ state_ref[idx] != state_new[idx] ||
+ c_ref.low != c_new.low ||
+ c_ref.range != c_new.range ||
+ c_ref.bytestream != c_new.bytestream)
+ fail();
+ }
+
+ if (checkasm_bench_func()) {
+ memcpy(state_new, state_ref, sizeof(state_ref));
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+ bench_new(&c_new, &state_new[0]);
+ }
+ }
+}
+
+static void check_get_cabac_bypass(int use_wasm)
+{
+ uint8_t buf[CABAC_BUF_SIZE];
+ CABACContext c_ref, c_new;
+ declare_func(int, CABACContext *);
+ func_type *func = use_wasm ?
+#if ARCH_WASM
+ ff_get_cabac_bypass_wasm
+#else
+ get_cabac_bypass_c
+#endif
+ : get_cabac_bypass_c;
+
+ fill_random(buf, sizeof(buf));
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+
+ if (check_func(func, "cabac.bypass")) {
+ for (int i = 0; i < CABAC_BIN_COUNT; i++) {
+ int ret_ref = call_ref(&c_ref);
+ int ret_new = call_new(&c_new);
+
+ if (ret_ref != ret_new ||
+ c_ref.low != c_new.low ||
+ c_ref.range != c_new.range ||
+ c_ref.bytestream != c_new.bytestream)
+ fail();
+ }
+
+ if (checkasm_bench_func()) {
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+ bench_new(&c_new);
+ }
+ }
+}
+
+static void check_get_cabac_bypass_sign(int use_wasm)
+{
+ uint8_t buf[CABAC_BUF_SIZE];
+ CABACContext c_ref, c_new;
+ declare_func(int, CABACContext *, int);
+ func_type *func = use_wasm ?
+#if ARCH_WASM
+ ff_get_cabac_bypass_sign_wasm
+#else
+ get_cabac_bypass_sign_c
+#endif
+ : get_cabac_bypass_sign_c;
+
+ fill_random(buf, sizeof(buf));
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+
+ if (check_func(func, "cabac.bypass_sign")) {
+ for (int i = 0; i < CABAC_BIN_COUNT; i++) {
+ int val = (rnd() & 0x7FFF) + 1;
+ int ret_ref = call_ref(&c_ref, val);
+ int ret_new = call_new(&c_new, val);
+
+ if (ret_ref != ret_new ||
+ c_ref.low != c_new.low ||
+ c_ref.range != c_new.range ||
+ c_ref.bytestream != c_new.bytestream)
+ fail();
+ }
+
+ if (checkasm_bench_func()) {
+ int val = 1234;
+ setup_contexts(&c_ref, &c_new, buf, sizeof(buf));
+ bench_new(&c_new, val);
+ }
+ }
+}
+
+void checkasm_check_cabac(void)
+{
+ int use_wasm = !!(av_get_cpu_flags() & AV_CPU_FLAG_SIMD128);
+
+ check_get_cabac(use_wasm);
+ check_get_cabac_bypass(use_wasm);
+ check_get_cabac_bypass_sign(use_wasm);
+ report("cabac");
+}
+
+#else /* !ARCH_WASM */
+
+void checkasm_check_cabac(void)
+{
+ report("cabac");
+}
+
+#endif /* ARCH_WASM */
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 268e600346..aab943ff63 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -181,6 +181,9 @@ static const struct {
#if CONFIG_H264QPEL
{ "h264qpel", checkasm_check_h264qpel },
#endif
+ #if CONFIG_CABAC
+ { "cabac", checkasm_check_cabac },
+ #endif
#if CONFIG_HEVC_DECODER
{ "hevc_add_res", checkasm_check_hevc_add_res },
{ "hevc_deblock", checkasm_check_hevc_deblock },
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index a54231dd0d..b17967ac1b 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -112,6 +112,7 @@ void checkasm_check_hevc_deblock(void);
void checkasm_check_hevc_idct(void);
void checkasm_check_hevc_pel(void);
void checkasm_check_hevc_sao(void);
+void checkasm_check_cabac(void);
void checkasm_check_hpeldsp(void);
void checkasm_check_huffyuvdsp(void);
void checkasm_check_idctdsp(void);
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-12-15 8:54 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-15 8:53 [FFmpeg-devel] [PATCH] avcodec/wasm: WebAssembly-friendly scalar CABAC fast path optimization (PR #21202) xiatao via ffmpeg-devel
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git