SIMD primitives for MoonBit across wasm / wasm-gc / native / js — @simdcore, @simd_buffer, codecs, hashes, images, JSON indexing, and the experimental mizchi/simd/json parser.
just test # all 4 targets
just bench-wasm # wasm benchmark
just bench-native # native benchmark| package | wasm | wasm-gc | native | js | docs |
|---|---|---|---|---|---|
| @simdcore — faster core idioms | ✅ | · | ✅¹ | · | README |
| @simdimage — image / pixel ops | ✅ | · | · | · | README |
| @simd_buffer — portable buffer family | ✅ | ✅ | ✅ | · | README |
| @simdjson — JSON indexing | ✅ | ✅ | · | · | README |
| @simdcodec — byte codecs (base64) | ✅ | · | ⚠️² | · | README |
| @simdhash — SHA-256 / SHA-512 / SHA-1 / MD5 | ✅³ | · | ✅³ | · | README |
| @simd — FixedArray root API | ✅ | · | ✅ | · | this file |
"deps": {
"mizchi/simd": "0.4.0"
}import {
"mizchi/simd/src/simdcore",
"mizchi/simd/src/simdimage",
"mizchi/simd/src/simd_buffer",
"mizchi/simd/src/simdjson",
"mizchi/simd/src/simdcodec",
"mizchi/simd/src/simdhash",
}let a : FixedArray[Int] = [1, 2, 3, 4, 5, 6, 7, 8]
let buf = @simd_buffer.SimdBuffer::from_array(a)
let total = buf.sum() // SIMD on wasm / wasm-gc / native, scalar on js
let out = @simd_buffer.SimdBuffer::make(buf.length())
@simd_buffer.SimdBuffer::add(buf, buf, out)
let back : FixedArray[Int] = out.to_array()| target | storage | SIMD path | notes |
|---|---|---|---|
| wasm | linear memory | inline-WAT v128.* | fastest (3-90× over scalar) |
| wasm-gc | linear memory | inline-WAT v128.* | parity with wasm |
| native | FixedArray | C FFI — NEON on arm64, SSE2 baseline on any x86-64 across i32/f64/byte ops | real SIMD; parity with the FixedArray-API native fast paths |
| js | FixedArray | scalar only — no SIMD acceleration. See note below |
js is scalar. MoonBit on the js backend has no SIMD escape hatch. SimdBuffer compiles and runs on js for API portability (same code across all four targets), but throughput-critical hot paths on js should keep data in native JS typed arrays and call back into wasm where SimdBuffer is actually accelerated.
a.iter().fold(init=0, fn(x, y) { x + y }) // core
@simdcore.sum(a) // faster equivalent
a.iter().maximum() -> @simdcore.maximum(a) // Int?
a.search(x) -> @simdcore.search(a, x) // Int?
a.sort() -> @simdcore.sort(a) // FixedArray[Int]| op | core idiom | @simdcore | x |
|---|---|---|---|
| sum (n=1024) | iter().fold 16.3 µs | 230 ns | 71 |
| maximum | iter().maximum() 19.1 µs | 236 ns | 81 |
| sort | Array::sort 264 µs | 29.6 µs | 8.9 |
| add (i32 element-wise) | zip loop 3.26 µs | 302 ns | 10.8 |
| bytes_is_ascii (4 KiB) | 5.08 µs | 241 ns | 21 |
| bytes_index_of (4 KiB) | 12.6 µs | 257 ns | 49 |
| encode_utf8_into (4 KiB ASCII) | 9.13 µs | 835 ns | 10.9 |
| json_classify_structural (4 KiB) | 19.0 µs | 1.81 µs | 10.5 |
let rgb = Bytes::from_array(pixels) // n*3 RGB bytes
let rgba : FixedArray[Byte] = FixedArray::make(n * 4, b'\x00')
@simdimage.rgb_to_rgba(rgb, 0xFF, rgba) // expand to RGBA, opaque| op | what it does |
|---|---|
| rgb_to_rgba(src, alpha, out) | 3 byte/px RGB → 4 byte/px RGBA, constant alpha |
| rgba_to_grayscale(src, out) | Rec. 601 Y = (77R+150G+29B) >> 8 |
| channel_extract(src, ch, out) | pull one RGBA channel into a planar buffer |
| channel_merge(r, g, b, a, out) | interleave 4 planar streams → RGBA |
| lerp(a, b, t, out) | (a*(256-t) + b*t) >> 8, t ∈ 0..=256 |
| alpha_blend_solid(dst, r, g, b, a) | in-place premultiplied source-over of a solid color |
| histogram(src, bins) | 256-bin byte histogram (scalar — no SIMD scatter) |
| op | scalar | SIMD | x |
|---|---|---|---|
| rgb_to_rgba | 25.6 µs | 1.63 µs | 15.7 |
| alpha_blend_solid | 57.3 µs | 3.09 µs | 18.5 |
| lerp (4096 B) | 12.0 µs | 866 ns | 13.9 |
| channel_merge | 27.0 µs | 2.54 µs | 10.6 |
| channel_extract | 7.33 µs | 1.10 µs | 6.7 |
| rgba_to_grayscale | 16.2 µs | 4.84 µs | 3.3 |
let input = @simd_buffer.SimdBufferBytes::from_array(json_bytes)
let words = (input.length() + 31) / 32
let structural = @simd_buffer.SimdBuffer::make(words)
let quote_mask = @simd_buffer.SimdBuffer::make(words)
let indices = @simd_buffer.SimdBuffer::make(input.length())
let count = @simdjson.find_structural_indices_with_scratch(
input, structural, quote_mask, indices,
)
// indices.get(0..count) now hold byte offsets of `{ } [ ] , :` outside any string| op | what it does | wasm vs native scalar |
|---|---|---|
| classify_structural | bitmask of { } [ ] , : positions | 6.9× |
| classify_numeric | bitmask of 0-9 - + . e E positions | 5.6× |
| classify_quote_raw | bitmask of " positions (raw, pre-escape) | 6.6× |
| classify_backslash | bitmask of \ positions | similar |
| compute_quote_mask | in-string mask honouring \" / \\ escapes | 0.46× (loses — see below) |
| extract_structural_indices | bit-walk → byte offsets via i32.ctz | 11× |
| find_structural_indices_with_scratch | full pipeline | 1.27× end-to-end |
let ring = @simd_buffer.SimdBufferRing::make(65536)
for input in inputs {
ring.reset()
let out = ring.alloc_bytes((input.length() + 2) / 3 * 4)
@simd_buffer.SimdBufferBytes::base64_encode_into(input, out)
// ... use out, then forget it ...
}let arr : FixedArray[Int] = [1, 2, 3, 4, 5, 6, 7, 8]
let total = @simd.sum_i32(arr) // wasm SIMD, scalar on others| target | acceleration |
|---|---|
| wasm | inline-WAT v128 SIMD (real SIMD) |
| wasm-gc | scalar fallback (GC-ref FFI blocks v128.load) |
| native | C FFI — NEON (arm64) / SSE2 baseline (x86-64) across the i32 / f64 / byte op surface; reductions, element-wise, image, base64 all wired to the FFI |
| js | scalar fallback |
| op | size | scalar | SIMD | x |
|---|---|---|---|---|
| sum_i32 | 1024 | 693 ns | 132 ns | 5.2 |
| add_i32 | 1024 | 1.54 µs | 132 ns | 11.7 |
| adler32 | 4096 B | 9.58 µs | 358 ns | 26.8 |
| memcpy | 4096 B | 5.44 µs | 86 ns | 63 |
| memset | 4096 B | 6.03 µs | 67 ns | 90 |
| matmul_f64 | 64×64 | 359 µs | 65 µs | 5.5 |
| base64_encode | 4096 B | 7.45 µs | 2.06 µs | 3.6 |
| sort_i32 | 1024 | 157 µs | 16.4 µs | 9.6 |
| op | size | scalar fallback | SimdBuffer SIMD | x |
|---|---|---|---|---|
| sum_i32 | 1024 | 344 ns | 106 ns | 3.2 |
| add_i32 | 1024 | 413 ns | 118 ns | 3.5 |
| popcount_bytes | 4096 | 6.91 µs | 291 ns | 24 |
| memcpy | 4096 | 1.17 µs | 100 ns | 12 |
| memset | 4096 | 1.00 µs | 59 ns | 17 |
| adler32 | 4096 | 9.23 µs | 329 ns | 28 |
| matmul_f64 | 64×64 | (scalar) | 57.9 µs | ~5 |
src/
# FixedArray-API root package — imported as @simd
simd_wasm_{i32,f64,f32,bytes,sort}.mbt # wasm inline-WAT v128
simd_native.mbt + simd_native_ffi.mbt # native extern "C"
simd_native.c # NEON / SSE2-baseline intrinsics
simd_scalar.mbt # js + wasm-gc fallback
internal/scalar*.mbt # shared scalar reference impls
simdcore/ # @simdcore — faster core equivalents
simdcore.mbt # facade (i32 / f64 / Bytes / Array)
simdcore_str_{wasm,fallback}.mbt # String ↔ UTF-8
simdcore_json_{wasm,native,fallback}.mbt + simdcore.c # JSON indexing
simdimage/ # @simdimage — image / pixel ops
simdimage.mbt # public API (Bytes / FixedArray[Byte])
simdimage_wasm.mbt # wasm inline-WAT v128
simdimage_fallback.mbt # wasm-gc + native + js scalar
simdcodec/ # @simdcodec — byte codecs (base64)
base64_common.mbt # length helpers + encode/decode wrappers
base64_wasm.mbt # wasm SIMD encode_into/decode_into
base64_fallback.mbt # scalar tables + reference (all but native)
base64_scalar.mbt # wasm-gc + js encode_into/decode_into
base64_native.mbt + base64.c # native C FFI (gcc scalar, LUT decode)
simdhash/ # @simdhash — SHA-256 / SHA-1 / MD5
simdhash.mbt + sha1/md5/sha512.mbt # scalar digests + public API
simdhash_wasm.mbt # wasm 4-way inline-WAT (sha256/sha1/md5 _x4)
simdhash_native.mbt + simdhash.c # native SSE2/NEON 4-way (sha256/sha1)
simdjson/ # @simdjson — JSON byte classification
simdjson_wasm.mbt # wasm + wasm-gc inline-WAT
simdjson_scalar.mbt # native + js scalar
simd_buffer/ # @simd_buffer — portable API
simd_buffer.mbt / _f32 / _f64 / _bytes / _sort / _ring / _copy.mbt
# wasm + wasm-gc: linear-memory storage + inline-WAT v128
simd_buffer_scalar.mbt
# native + js: FixedArray storage + @internal / @simdcodec delegation
simd_buffer_imports.mbt
# cross-target import keep-aliveSIMD primitives for MoonBit across wasm / wasm-gc / native / js — @simdcore, @simd_buffer, codecs, hashes, images, JSON indexing, and the experimental mizchi/simd/json parser.