RoaringBitmap / RoaringBitmap/CRoaring

WASM SIMD128 backend

Open
#801 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
1.9k
Forks
334
Avg merge
2d 5h
Merged PRs (30d)
17

Description

Is your feature request related to a problem? Please describe.

When CRoaring is compiled for wasm32 (e.g. wasm32-wasi for browser / Node consumption), every container loop falls back to scalar — the x86 (CROARING_IS_X64) and ARM (__ARM_NEON) SIMD paths in include/roaring/portability.h do not fire, and there is no WASM SIMD128 equivalent. Browser-deployed CRoaring therefore runs the same bitset AND / OR / XOR / ANDNOT and popcount inner loops as a native scalar build, even though WASM SIMD128 has been broadly shipped since 2022 (Chrome 91+, Firefox 122+, Safari 16.4+).

We've confirmed this is not something Clang's auto-vectorizer recovers from. Compiling CRoaring v4.6.1 with -msimd128 and inspecting the output with wasm-objdump -d:

# at -Os (36,297 bytes):
$ wasm-objdump -d facetsearch-core.wasm \
    | grep -oE 'v128\.[a-z0-9._]+|[fi][0-9]+x[0-9]+\.[a-z0-9._]+' | wc -l
0

# at -O3 (846,074 bytes):
0

Zero v128 opcodes at either level. The container loops are too branch-heavy for scalar-evolution vectorization; a hand-written SIMD backend is required to close the gap.

Describe the solution you'd like

A third arch branch in portability.h alongside the existing two:

#if defined(__wasm_simd128__)
#define CROARING_IS_WASM 1
#include <wasm_simd128.h>
#endif

…and WASM-SIMD versions of the container-internal hot loops that already have SSE2 counterparts. SSE2 → WASM SIMD128 maps directly for most of the ops CRoaring uses:

SSE2 WASM SIMD128
_mm_and_si128 wasm_v128_and
_mm_or_si128 wasm_v128_or
_mm_xor_si128 wasm_v128_xor
_mm_andnot_si128 wasm_v128_andnot
_mm_loadu_si128 wasm_v128_load
_mm_storeu_si128 wasm_v128_store
__builtin_popcountll × 2 wasm_i64x2_popcnt (v128 popcount is 8-bit; bitset popcount needs a short reduce)

Rationale / expected objective benefit

The highest-leverage targets are bitset AND / OR / XOR / ANDNOT in src/containers/bitset.c and the bitset cardinality popcount. Based on the SSE2 vs scalar delta on the same loops on native builds, we'd expect roughly 2–4× on bitset-heavy workloads once WASM SIMD128 is used — exactly the speedup x86/ARM users already
get.

Expected concrete wins for browser CRoaring consumers:

  • Bitset intersection / union / xor / andnot — 4× 64-bit lanes per 128-bit op; currently 1-lane scalar.
  • Bitset cardinality (roaring_cardinality, intersect_cardinality, union_cardinality)wasm_i64x2_popcnt vectorizes the hot popcount loop.
  • Array container intersection / union (sorted u16)wasm_i16x8_* min/max/eq for the galloping-search and shuffle- merge steps. Matches the SSE2 implementation in src/containers/array.c.

Describe alternatives you've considered

  1. Enabling -msimd128 and relying on Clang's auto-vectorizer. Measured above: zero v128 opcodes produced, at any optimization level. Auto-vectorization is not a viable alternative for the container loops.

  2. SIMDe shim layer (simde/wasm/simd128.h for SSE2-style intrinsics, or simde/x86/sse2.h mapped to WASM SIMD when __wasm_simd128__ is set). Would in principle let the existing SSE2 code paths compile on WASM with minimal changes, but adds a header-only dependency and its code-gen on the bitset popcount path is known to produce slightly worse shuffles than hand-written wasm_i64x2_popcnt. Mentioning it for completeness — it's the way to unblock this without a full per-op port.

Additional context

  • Tested CRoaring version: v4.6.1 (tarball sha256 dd5c73c464be2df05b3472cce00d2d753b7eed0856752d88b23058aaff542216).
  • Toolchain: Zig 0.13.0 vendoring Clang 17, target wasm32-wasi, reactor exec model.
  • Runtime support: browsers with WASM SIMD128 today cover >95% of global traffic. No runtime dispatch is needed if a consumer ships two builds (SIMD + scalar), same model as x86 SSE2 vs AVX2; or the SIMD build can simply be the baseline for evergreen-browser-only projects.

Are you willing to contribute code or documentation toward this new feature?

Yes

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the architecture branches in include/roaring/portability.h, then inspect the SSE2 hot loops in src/containers/bitset.c and src/containers/array.c. Review how the existing counterparts handle bitwise operations, loads, stores, and cardinality, and check wasm-objdump output from a wasm32 build. Done means the targeted loops emit WASM SIMD128 instructions and preserve the existing behavior and performance goals.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, wasm
Domain
performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.