Skip to content

vecmath: native WebAssembly SIMD128 backend - #4052

Merged
borisbat merged 5 commits into
masterfrom
bbatkin/wasm-vecmath
Sep 17, 2026
Merged

borisbat merged 5 commits into
masterfrom
bbatkin/wasm-vecmath

Conversation

@borisbat

Copy link
Copy Markdown
Collaborator

Behavior change: on the web build int(x) of a float outside the 32-bit range now saturates (NaN gives 0) instead of answering INT_MIN - the NEON contract; nothing to do unless a script relied on the sentinel.

Why. The web build ran vecmath's SSE2 arm through emscripten's -msse2 compat layer. There a float-to-int conversion is a per-lane lrint loop (86 wasm instructions for v_cvti_vec4i, 170 for v_cvt_roundi, 91 for v_floor), and min, max, select, abs and mul+add each cost two to four ops.

What changes.

  • include/vecmath/dag_vecMath_wasm.h implements the backend contract on wasm SIMD128: trunc_sat, floor/nearest, pmin/pmax with swapped operands (the SSE NaN and signed-zero rule), bitselect, one i8x16.shuffle per permutation.
  • vec4f/vec4i are clang typed vectors, selected on __wasm_simd128__ ahead of the __SSE2__ the compat layer predefines; daScriptC.h spells the same types for C.
  • web/ and the dasImgui wasm archives drop -msse2; relaxed SIMD is an opt-in knob, OFF because Safari refuses any relaxed opcode.
  • The backend battery gains a wasm arm and rows for the saturating converts, the pshufb key mask, the 64-bit shifts and other previously unpinned primitives; the wasm_build lane builds both arms and runs them under node.
  • The dasAudio convolution reverb takes its SIMD path on wasm; dasStbImage no longer forces its scalar paths there. Both were gated on the removed -msse2.

Observable behavior.

  • interpreter under node, float-to-int + floor + round loop: 26.4 ms -> 11.9 ms; n-bodies and the other profile samples unchanged
  • int(3e9) on the web build: INT_MIN -> INT_MAX (saturating, as on NEON)
  • daslang_static.wasm carries no relaxed opcode before or after

Where to look. The operand swap in v_min/v_max, the sign-bit shift in v_sel, the shift-count guards, and vd_cvt_to_vec4i's lane join in dag_vecMath_double.h.

Validation, claims, ledger

Validation

  • The wasm arm can fail only in .github/workflows/wasm_build.yml job wasm_build (the native ctest -L small builds the same file with EXPECT_WASM undefined). Ran on Windows, one of the three matrix cells, in web/cmake_temp: ninja test_vecmath_native test_vecmath_scalar, then from web/: "$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_native.js and ..._scalar.js - both all vecmath backend checks passed, exit 0; "$EMSDK_NODE" --experimental-wasm-exnref test/dastest_wasm.js ../ ./output - 1696 passed, 0 failed. EMSDK_PYTHON pointed at python 3.13 (the emsdk-bundled 3.9 is below emscripten's floor). Linux and macOS cells: the lane's run on the head commit.
  • Native: full MSVC Release build, ctest -C Release -L small 150 passed (includes vecmath_native and vecmath_scalar, rerun after the new rows), dastest --test tests/language 1758 passed. The preflight tests lane selected nothing (no .das changed) and the full preflight chain was not run: the diff reaches only the wasm lane and the vecmath headers, whose x86 and NEON arms gain preprocessor gates and no code.
  • No relaxed opcode in the built module: llvm-objdump -d daslang_static.wasm | grep -c relaxed is 0 for both the baseline and this branch.
  • Woodpecker round on 9eb6980: one P2 finding (the test targets' link options dropped the memory64 and pthread link flags), fixed; no second round - the fix batch is build plumbing, two preprocessor gates and test rows.

Claims - stated, not tested

  • The dasAudio convolution reverb's SIMD path and dasStbImage's wasm SIMD paths compile and run on wasm: verified by the web build and the node suite, not by an audio or image test under node. A break would be a compile error in libDasModuleAudio / libDasModuleStbImage on the wasm_build lane or a wrong reverb/decode result in the playground.
  • DAS_WASM_MEMORY64=ON / DAS_WASM_PTHREADS=ON builds of the two test arms link: the link option list carries -sMEMORY64=2 / -pthread from the same knobs, not built here.
  • The relaxed-SIMD arm of v_madd/v_nmsub (-mrelaxed-simd, opt-in) compiles: built by hand once for the instruction-count probe, no lane builds it.

Not done

  • Upstream contribution of dag_vecMath_wasm.h to DagorEngine prog/1stPartyLibs/vecmath after this lands.
  • Declined folds from the dupe audit: sharing the __builtin_shufflevector perm block with the NEON arm, moving the v_hmin/v_check_*/v_round family into dag_vecMath_common.h, and the vd_dot* one-liners into the double header's shared tail - each touches the other backends' arms, which go upstream as they are.
  • The target auto-detect ladder stays duplicated between dag_vecMathDecl.h and daScriptC.h (pre-existing; a shared C-compatible header is the fold).
  • web/ has no ARCHITECTURE.md; its README.md cannot take anchored facts, so web/CMakeLists.txt's why-comments stay comments.
  • The JIT's wasm feature string (+simd128,+nontrapping-fptoint) is unchanged; JIT-emitted code keeps mul+add, matching the runtime archive.

borisbat and others added 5 commits September 16, 2026 22:42
… web build ran the SSE2 arm through emscripten's xmmintrin compat layer, where float->int conversion is a per-lane lrint loop (v_cvti_vec4i 86 instructions, v_cvt_roundi 170, v_floor 91) and min/max, select, abs and mul+add each cost two to four ops; the new arm answers with the ISA's own instructions (trunc_sat, floor/nearest, pmin/pmax with swapped operands for the SSE NaN and signed-zero rule, bitselect, f32x4.relaxed_madd for v_madd/v_nmsub under -mrelaxed-simd). vec4f/vec4i are clang typed vectors so the overload set stays distinct, selected on __wasm_simd128__ ahead of the __SSE2__ the compat layer predefines; daScriptC.h spells the same typedefs for C. Converts saturate and _x forms are the packed op (the NEON contract), everything else keeps the SSE rows. web/CMakeLists.txt and the dasImgui wasm archives drop -msse2 and gain DAS_WASM_RELAXED_SIMD / DAS_IMGUI_WASM_RELAXED_SIMD (default ON; an engine without relaxed SIMD refuses the module - Safari only behind a flag). Under emscripten the backend battery builds as node-runnable pairs under web/output/tests and the wasm_build lane runs both arms. Interpreter under node: the float->int/floor/round loop 26.4 -> 11.9 ms, n-bodies and the other profile samples unchanged within noise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…_IMGUI_WASM_RELAXED_SIMD default OFF and cite the same ruling the JIT's feature string follows (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md sec.12) - WebKit implements none of the relaxed opcodes, one anywhere in the module makes Safari refuse it at validation, and the browser builds ship to every engine. The module now carries no relaxed opcode; the backend battery, the language suite under node and the converted-op timings (float->int/floor/round loop 11.8 ms) are unchanged, so the fused madd was buying nothing measurable in the interpreter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ompresses to pointers at the wasm feature string section, two restatements of dag_vecMath_wasm.h's own header block go, the relaxed-SIMD default and the web/dasImgui flag mirror become REVIEW.md rules, include/vecmath/CLAUDE.md says the folder is authored here for upstream and keeps the sibling backends' comment shape, the emscripten test block no longer names the native arm when the scalar backend is forced, and preflight.md's wasm_build row carries the node battery

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…asm.h joins the installed vecmath header set, the dasAudio convolution reverb takes its SIMD path on wasm and dasStbImage stops forcing its scalar paths there (both gated on the -msse2 the arc removed), the two test arms keep the memory64 and pthread link flags their objects were compiled with, the battery gains rows for the saturating converts, the pshufb key mask, the 64-bit shifts, mulhi16, cvt_byte, perm_zayx, the byte interleave, mat_43cu_from_mat44 and v_check_xz_all_true, the arm's reciprocal forms and is_neg_special delegate instead of repeating a body and the pair ops spell their lane fold through v_perm_xzac/ywbd, the vecmath notes correct the v_sel trap (every backend reads the sign bit; v_btsel is the per-bit select) and state the backend comment shape without an exemption, the hygiene skill's vendoring clause gains its outbound direction, the wasm_build lane runs the language suite before the battery, and the web, dasImgui, tests-cpp and workflows checklists carry the dragon's rewordings

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…uments: the workflows checklist's preflight duty splits into the row rule and the section rule (build.yml carries one section per job, not per matrix arm), the dasImgui checklist gains the relaxed-SIMD ban its web twin already had and both mirror rules key on the -m flag list alone so a default flip has one verdict, the vecmath notes say which comments travel upstream, and the hygiene skill's outbound clause is its own rule without a folder census

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 06:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Vecmath README updates are missing the -mnontrapping-fptoint requirement even though the wasm backend depends on trunc_sat conversions enabled by that feature.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a native WebAssembly SIMD128 backend for vecmath (replacing reliance on Emscripten’s SSE2 compatibility layer), updates wasm build flags accordingly, and extends CI/tests/docs to validate and explain the new wasm SIMD behavior (notably saturating float→int conversions on web builds).

Changes:

  • Implement include/vecmath/dag_vecMath_wasm.h and wire _TARGET_SIMD_WASM auto-detect + vec4d support for wasm.
  • Drop -msse2 from web/ and dasImgui wasm builds; add an opt-in -mrelaxed-simd knob (default OFF for Safari compatibility).
  • Extend vecmath backend battery + wasm CI lane to run wasm-native and scalar arms under Node.
File summaries
File Description
web/REVIEW.md Adds checklist constraints around relaxed-SIMD defaults and keeping wasm feature flags in sync with dasImgui.
web/CMakeLists.txt Removes -msse2, adds DAS_WASM_RELAXED_SIMD option and -mrelaxed-simd gating.
tests-cpp/REVIEW.md Clarifies “big” checklist applicability based on ctest labels rather than local CMake ownership.
tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp Updates backend battery semantics/guards for wasm (packed _x ops + saturating converts) and adds new pinned rows.
tests-cpp/big/vecmath_backend/CMakeLists.txt Ensures wasm builds explicitly build/link the vecmath test arms into web/output/tests with appropriate link options.
skills/internal/preflight.md Updates documentation of wasm_build.yml to include the Node-run language suite + vecmath battery.
skills/comment_style_hygiene.md Adds a rule about preserving upstream comment/shape conventions for upstream-contributed code.
modules/dasStbImage/CMakeLists.txt Removes wasm-only scalar-forcing defines now that -msse2 is no longer used on web builds.
modules/dasImgui/REVIEW.md Mirrors relaxed-SIMD “default OFF” constraints and cross-folder flag sync requirements.
modules/dasImgui/CMakeLists.txt Drops -msse2 and adds opt-in relaxed-SIMD flag for wasm archives.
modules/dasAudio/src/convolution_reverb.h Enables wasm SIMD path via _TARGET_SIMD_WASM gating.
include/vecmath/usage.md Updates vecmath usage docs to include wasm SIMD128 among supported backends.
include/vecmath/README.md Updates high-level README to include wasm SIMD128 in supported targets and describes wasm flags (see review comment re: -mnontrapping-fptoint).
include/vecmath/dag_vecMathDecl.h Adds _TARGET_SIMD_WASM auto-detect and wasm typed-vector definitions for vec4f/vec4i/vec4d.
include/vecmath/dag_vecMath.h Includes the new wasm backend header when _TARGET_SIMD_WASM is selected.
include/vecmath/dag_vecMath_wasm.h New wasm SIMD128 backend implementing vecmath primitive contract (including saturating float→int).
include/vecmath/dag_vecMath_double.h Adds wasm SIMD128 implementation for the vec4d layer.
include/vecmath/dag_vecMath_const.h Extends constant definitions to cover the wasm backend.
include/vecmath/dag_vecMath_common.h Aligns shared helpers to treat wasm like SSE where semantics match.
include/vecmath/CLAUDE.md Updates module overview and backend contract notes to include wasm.
include/daScript/daScriptC.h Mirrors wasm target auto-detect and defines wasm vec4f/vec4i types for the C API.
CMakeLists.txt Adds the new vecmath wasm header to the build’s vecmath source list.
.github/workflows/wasm_build.yml Builds vecmath test arms explicitly and runs vecmath battery + language suite under Node.
.github/workflows/REVIEW.md Tightens workflow-doc update requirements (row + per-workflow section in skills/internal/preflight.md).
Review details
  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread include/vecmath/README.md
@borisbat
borisbat merged commit c57d647 into master Sep 17, 2026
38 checks passed
@borisbat
borisbat deleted the bbatkin/wasm-vecmath branch September 17, 2026 07:57
borisbat added a commit that referenced this pull request Sep 17, 2026
…RC2: the bundle smoke compiles the DAP bridge, the watchdog's interpreter form, the LLVM-AOT driver and the LSP subtools, checks the static watchdog is present and answers --help and --lsp from the bundle, and launches the DAP bridge on an empty stdin; the release audit's utils phase gains rows for the DAP bridge's argument gate (a row now carries the exit code it expects) and both watchdog forms, and its telegram rows - stale since the echo-bot moved to its package repo at RC1 - become the crash example, whose native dascrash package installs from a local path and so proves the bundle's C++ build kit offline. The daslang driver no longer answers -h/--help placed after the -- separator, which stole every interpreted clargs tool's help (tests/fio pins it). The audit's per-file timeout is 180 s: a dasLLAMA example compiles cold in about 40 s alone and the 60 s cap tripped under a full worker set; examples/dasLLAMA/library/main.das is ruled integration-scaffold tier. CHANGELIST gains #4046 and #4052.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants