From f340a859db7e079594392f816f5ef45cef951881 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 22:42:27 -0700 Subject: [PATCH 1/5] vecmath: native WebAssembly SIMD128 backend (dag_vecMath_wasm.h). The 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 --- .github/workflows/wasm_build.yml | 4 + include/daScript/daScriptC.h | 18 +- include/vecmath/CLAUDE.md | 11 +- include/vecmath/README.md | 25 +- include/vecmath/dag_vecMath.h | 2 + include/vecmath/dag_vecMathDecl.h | 41 +- include/vecmath/dag_vecMath_common.h | 2 +- include/vecmath/dag_vecMath_const.h | 2 +- include/vecmath/dag_vecMath_double.h | 122 ++++ include/vecmath/dag_vecMath_wasm.h | 642 ++++++++++++++++++ include/vecmath/usage.md | 8 +- modules/dasImgui/CMakeLists.txt | 9 +- tests-cpp/big/vecmath_backend/CMakeLists.txt | 14 + .../vecmath_backend/test_vecmath_backend.cpp | 16 +- web/CMakeLists.txt | 10 +- 15 files changed, 893 insertions(+), 33 deletions(-) create mode 100644 include/vecmath/dag_vecMath_wasm.h diff --git a/.github/workflows/wasm_build.yml b/.github/workflows/wasm_build.yml index db44fe83c0..29cb60b1d8 100644 --- a/.github/workflows/wasm_build.yml +++ b/.github/workflows/wasm_build.yml @@ -151,6 +151,8 @@ jobs: cd cmake_temp cmake -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake_preset }} -DDAS_FLEX_BISON_DISABLED=ON -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ ninja + # the vecmath backend battery, both arms as node-runnable pairs (tests-cpp/big/vecmath_backend) + ninja test_vecmath_native test_vecmath_scalar - name: "Test: hello world via Node.js" run: | @@ -162,6 +164,8 @@ jobs: cd web # Modern wasm EH proposal (try_table / exnref) is gated in Node 22. # Default-on in Node 24+. Force-enable for forward compat with current LTS. + "$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_native.js + "$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_scalar.js "$EMSDK_NODE" --experimental-wasm-exnref test/dastest_wasm.js ../ ./output ########################################################### diff --git a/include/daScript/daScriptC.h b/include/daScript/daScriptC.h index 8bb2007147..27942a154b 100644 --- a/include/daScript/daScriptC.h +++ b/include/daScript/daScriptC.h @@ -21,8 +21,15 @@ #else #define DAS_CC_API #endif -//if target is not defined, try to auto-detect target -#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_SCALAR) +//if target is not defined, try to auto-detect target (same order as vecmath/dag_vecMathDecl.h: +//wasm first, its -msse* compat layer predefines __SSE2__) +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) + #if defined(__wasm_simd128__) + #define _TARGET_SIMD_WASM 1 + #endif +#endif + +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) #if __SSE4_1__ || defined(__AVX__) || defined(__AVX2__) #define _TARGET_SIMD_SSE 4 #elif __SSSE3__ @@ -32,7 +39,7 @@ #endif #endif -#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) #if defined(__ARM_NEON) || defined(__ARM_NEON__) #define _TARGET_SIMD_NEON 1 #else @@ -48,6 +55,11 @@ #include typedef float32x4_t vec4f; typedef int32x4_t vec4i; +#elif defined(_TARGET_SIMD_WASM) + // the same clang typed vectors vecmath/dag_vecMathDecl.h declares, spelled for C too + #include + typedef float vec4f __attribute__((__vector_size__(16), __aligned__(16))); + typedef int32_t vec4i __attribute__((__vector_size__(16), __aligned__(16))); #elif defined(_TARGET_SIMD_SCALAR) // shared with vecmath/dag_vecMathDecl.h: same tag names, members and layout - whichever // header is included first defines the pair for both (guard macro is vecmath-owned) diff --git a/include/vecmath/CLAUDE.md b/include/vecmath/CLAUDE.md index eae399a274..0b279179ad 100644 --- a/include/vecmath/CLAUDE.md +++ b/include/vecmath/CLAUDE.md @@ -1,14 +1,14 @@ # vecmath - SIMD Math Library ## Overview -Platform-abstracted SIMD vector math library. Wraps SSE2/SSSE3/SSE4.1 (x86), NEON (ARM) and a -scalar per-lane fallback for targets with no SIMD ISA behind a +Platform-abstracted SIMD vector math library. Wraps SSE2/SSSE3/SSE4.1 (x86), NEON (ARM), +wasm SIMD128 and a scalar per-lane fallback for targets with no SIMD ISA behind a unified C API. Used pervasively throughout the Dagor Engine for all performance-critical math: transforms, physics, BVH traversal, culling, animation, etc. ## Key Types (dag_vecMathDecl.h) -- `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a 16-byte struct on scalar) -- `vec4i` -- 128-bit integer vector (__m128i / int32x4_t) +- `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a clang typed vector on wasm, a 16-byte struct on scalar) +- `vec4i` -- 128-bit integer vector (__m128i / int32x4_t / an int32 typed vector on wasm) - `mat33f` -- 3x3 column-major matrix (3 x vec3f) - `mat44f` -- 4x4 column-major matrix (4 x vec4f) - `mat43f` -- 4x3 row-major matrix (3 x vec4f, each row is xyzw where w = translation component) @@ -25,8 +25,9 @@ transforms, physics, BVH traversal, culling, animation, etc. | `dag_vecMath_const.h` | Constants: V_C_HALF, V_C_ONE, V_C_PI, V_C_UNIT_1000, V_CI_MASK*, etc. | | `dag_vecMath_pc_sse.h` | SSE low-level implementation of basic functions | | `dag_vecMath_neon.h` | NEON (ARM) low-level implementation of basic functions | +| `dag_vecMath_wasm.h` | WebAssembly SIMD128 implementation of basic functions (clang `-msimd128`; `-mrelaxed-simd` fuses v_madd/v_nmsub) | | `dag_vecMath_scalar.h` | Scalar per-lane implementation of basic functions (no-SIMD fallback, forceable with `_TARGET_SIMD_SCALAR=1`) | -| `dag_vecMath_double.h` | `vec4d` double-precision math (SSE/AVX, NEON and scalar in one file); include via dag_vecMath.h | +| `dag_vecMath_double.h` | `vec4d` double-precision math (SSE/AVX, NEON, wasm and scalar in one file); include via dag_vecMath.h | | `dag_vecMath_common.h` | Shared implementations (bbox, frustum, quat, matrix ops built on core intrinsics) without hw-specific intrinsics | | `dag_vecMath_trig.h` | Polynomial approximations for sin/cos/tan/atan/asin/acos | diff --git a/include/vecmath/README.md b/include/vecmath/README.md index f16c1d795f..7af9b03036 100644 --- a/include/vecmath/README.md +++ b/include/vecmath/README.md @@ -1,9 +1,9 @@ # vecmath A small, header-only SIMD vector math library with one portable API across x86 -(SSE2/SSSE3/SSE4.1), ARM (NEON / AArch64), and any other CPU through a scalar -per-lane backend. Write your math once; it compiles to -good vector code on desktop, consoles, and mobile. +(SSE2/SSSE3/SSE4.1), ARM (NEON / AArch64), WebAssembly (SIMD128), and any other +CPU through a scalar per-lane backend. Write your math once; it compiles to +good vector code on desktop, consoles, mobile, and the browser. vecmath is the math core of the [Dagor Engine](https://github.com/GaijinEntertainment/DagorEngine) and powers its transforms, physics, culling, and animation. This repository is the @@ -12,13 +12,13 @@ standalone, dependency-free version of those headers. ## Why - **One API, many CPUs.** You call `v_add`, `v_mat44_mul`, `v_norm3`. The header - selects the SSE or NEON implementation for whatever you build for. No `#ifdef` - soup in your own code. + selects the SSE, NEON or wasm implementation for whatever you build for. No + `#ifdef` soup in your own code. - **Header-only, no dependencies.** Add the include path and go. Nothing to build or link. -- **Zero-overhead.** Types are the native SIMD registers (`__m128` / `float32x4_t`), - passed in registers. Almost everything is force-inlined, so unused results melt - away and there is no wrapper-object cost. +- **Zero-overhead.** Types are the native SIMD registers (`__m128` / `float32x4_t` / + a `v128`-backed typed vector), passed in registers. Almost everything is + force-inlined, so unused results melt away and there is no wrapper-object cost. - **Batteries included.** Vectors, 3x3 / 4x3 / 4x4 matrices, quaternions, planes, bounding boxes and spheres, frustum culling, ray/triangle intersection, fast trig/exp approximations, and a double-precision `vec4d` layer. @@ -26,18 +26,23 @@ standalone, dependency-free version of those headers. ## Requirements - C++11 or later. -- An x86 target with at least SSE2, or an AArch64 (ARMv8) target with NEON; +- An x86 target with at least SSE2, an AArch64 (ARMv8) target with NEON, or a + WebAssembly target built with `-msimd128` (add `-mrelaxed-simd` for a fused + `v_madd`; an engine without the relaxed-SIMD proposal then refuses the module); any other target (Cortex-M, RISC-V without V, ...) uses the scalar backend, selected automatically or forced with `_TARGET_SIMD_SCALAR=1`. - MSVC, Clang, or GCC. The target ISA is auto-detected from the usual compiler macros (`__SSE4_1__`, -`__ARM_NEON`, ...). To pin it explicitly, define one of these before including: +`__ARM_NEON`, `__wasm_simd128__`, ...). To pin it explicitly, define one of these +before including: ```cpp #define _TARGET_SIMD_SSE 4 // 2 = SSE2, 3 = SSSE3, 4 = SSE4.1 // or #define _TARGET_SIMD_NEON 1 +// or +#define _TARGET_SIMD_WASM 1 ``` ## Getting started diff --git a/include/vecmath/dag_vecMath.h b/include/vecmath/dag_vecMath.h index 015c80d777..ef962f3cec 100644 --- a/include/vecmath/dag_vecMath.h +++ b/include/vecmath/dag_vecMath.h @@ -1688,6 +1688,8 @@ VECTORCALL VECMATH_FINLINE vec4d vd_length3_x(vec4d a); #include "dag_vecMath_pc_sse.h" #elif _TARGET_SIMD_NEON #include "dag_vecMath_neon.h" +#elif _TARGET_SIMD_WASM + #include "dag_vecMath_wasm.h" #elif _TARGET_SIMD_SCALAR #include "dag_vecMath_scalar.h" #else diff --git a/include/vecmath/dag_vecMathDecl.h b/include/vecmath/dag_vecMathDecl.h index 45c4f53812..815ab4ae9a 100644 --- a/include/vecmath/dag_vecMathDecl.h +++ b/include/vecmath/dag_vecMathDecl.h @@ -47,8 +47,16 @@ typedef const struct bsph3f& bsph3f_cref; # endif #endif -//if target is not defined, try to auto-detect target -#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_SCALAR) +//if target is not defined, try to auto-detect target. wasm comes first: emscripten's -msse* +//compat layer predefines __SSE2__ over the same SIMD128 instructions, and the native backend +//is the one that answers with single instructions +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) + #if defined(__wasm_simd128__) + #define _TARGET_SIMD_WASM 1 + #endif +#endif + +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) #if __SSE4_1__ || defined(__AVX__) || defined(__AVX2__) #define _TARGET_SIMD_SSE 4 #elif __SSSE3__ @@ -58,7 +66,7 @@ typedef const struct bsph3f& bsph3f_cref; #endif #endif -#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) +#if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) #if defined(__ARM_NEON) || defined(__ARM_NEON__) #define _TARGET_SIMD_NEON 1 #else @@ -149,6 +157,33 @@ typedef const struct bsph3f& bsph3f_cref; struct vec4d { float64x2_t xy, zw; }; #define VECMATH_VEC4D_256 0 +#elif _TARGET_SIMD_WASM + #include + #include + + //! clang typed vectors, so vec4f and vec4i stay distinct types for overloading (as + //! float32x4_t / int32x4_t are on NEON); every wasm_* intrinsic takes them through a free + //! (v128_t) cast. Shared with daScript/daScriptC.h, which spells the same typedefs in C. + typedef float vec4f __attribute__((__vector_size__(16), __aligned__(16))); + typedef vec4f vec3f; + typedef int32_t vec4i __attribute__((__vector_size__(16), __aligned__(16))); + + typedef const vec4f vec4f_const; + + typedef const union alignas(16) _vec4i_const_name + { + unsigned m128_u32[4]; + vec4i m128; + vec4f m128f; + operator vec4i() const { return m128; } + operator vec4f() const { return m128f; } + } vec4i_const; + + //! see the SSE branch above: two f64x2 registers, low pair .xy, high pair .zw + typedef double vecmath_f64x2 __attribute__((__vector_size__(16), __aligned__(16))); + struct vec4d { vecmath_f64x2 xy, zw; }; + #define VECMATH_VEC4D_256 0 + #elif _TARGET_SIMD_SCALAR #include diff --git a/include/vecmath/dag_vecMath_common.h b/include/vecmath/dag_vecMath_common.h index 8c83d47155..eed5866295 100644 --- a/include/vecmath/dag_vecMath_common.h +++ b/include/vecmath/dag_vecMath_common.h @@ -47,7 +47,7 @@ VECTORCALL VECMATH_FINLINE bool v_check_xy_all_true(vec4f a) { return v_extract_ VECTORCALL VECMATH_FINLINE bool v_check_xy_all_false(vec4f a) { return v_extract_xi64(v_cast_vec4i(a)) == 0; } VECTORCALL VECMATH_FINLINE bool v_check_xy_any_true(vec4f a) { return v_extract_xi64(v_cast_vec4i(a)) != 0; } -#if _TARGET_SIMD_SSE +#if _TARGET_SIMD_SSE || _TARGET_SIMD_WASM VECTORCALL VECMATH_FINLINE bool v_check_xz_all_true(vec4f a) { return (v_truemask(a) & 0b101) == 0b101; } #else VECTORCALL VECMATH_FINLINE bool v_check_xz_all_true(vec4f a) { return v_check_xyzw_all_true(v_perm_xxzz(a)); } diff --git a/include/vecmath/dag_vecMath_const.h b/include/vecmath/dag_vecMath_const.h index d748388492..6e186ad0ae 100644 --- a/include/vecmath/dag_vecMath_const.h +++ b/include/vecmath/dag_vecMath_const.h @@ -14,7 +14,7 @@ #define REPLICATE(v) v, v, v, v -#if _TARGET_SIMD_SSE || _TARGET_SIMD_SCALAR +#if _TARGET_SIMD_SSE || _TARGET_SIMD_SCALAR || _TARGET_SIMD_WASM DECL_VEC_CONST vec4f_const V_C_HALF = { REPLICATE(0.5f) }; DECL_VEC_CONST vec4f_const V_C_HALF_MINUS_EPS = { REPLICATE(0.5f - 1.192092896e-07f * 32) }; DECL_VEC_CONST vec4f_const V_C_ONE = { REPLICATE(1.0f) }; diff --git a/include/vecmath/dag_vecMath_double.h b/include/vecmath/dag_vecMath_double.h index f016a38607..732ebd9952 100644 --- a/include/vecmath/dag_vecMath_double.h +++ b/include/vecmath/dag_vecMath_double.h @@ -332,6 +332,128 @@ VECTORCALL VECMATH_FINLINE vec4d vd_cross3(vec4d a, vec4d b) { return vd_from_halves(r_lo, r_hi); } +#elif _TARGET_SIMD_WASM +// ------------------------------------------------------------------------------------------------ +// wasm SIMD128 (two f64x2 halves: .xy and .zw) +// ------------------------------------------------------------------------------------------------ +VECTORCALL VECMATH_FINLINE vecmath_f64x2 vd_lo(vec4d a) { return a.xy; } +VECTORCALL VECMATH_FINLINE vecmath_f64x2 vd_hi(vec4d a) { return a.zw; } +VECTORCALL VECMATH_FINLINE vec4d vd_from_halves(vecmath_f64x2 lo, vecmath_f64x2 hi) { vec4d r; r.xy = lo; r.zw = hi; return r; } +#define VECMATH_WASM_D(a) ((vecmath_f64x2)(a)) + +VECTORCALL VECMATH_FINLINE vec4d vd_zero() { vecmath_f64x2 z = VECMATH_WASM_D(wasm_f64x2_const_splat(0.0)); return vd_from_halves(z, z); } +VECTORCALL VECMATH_FINLINE vec4d vd_splats(double a) { vecmath_f64x2 s = VECMATH_WASM_D(wasm_f64x2_splat(a)); return vd_from_halves(s, s); } +VECTORCALL VECMATH_FINLINE vec4d vd_make_vec4d(double x, double y, double z, double w) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_make(x, y)), VECMATH_WASM_D(wasm_f64x2_make(z, w))); +} + +VECTORCALL VECMATH_FINLINE double vd_extract_x(vec4d a) { return wasm_f64x2_extract_lane(VECMATH_WASM_V(a.xy), 0); } +VECTORCALL VECMATH_FINLINE double vd_extract_y(vec4d a) { return wasm_f64x2_extract_lane(VECMATH_WASM_V(a.xy), 1); } +VECTORCALL VECMATH_FINLINE double vd_extract_z(vec4d a) { return wasm_f64x2_extract_lane(VECMATH_WASM_V(a.zw), 0); } +VECTORCALL VECMATH_FINLINE double vd_extract_w(vec4d a) { return wasm_f64x2_extract_lane(VECMATH_WASM_V(a.zw), 1); } + +VECTORCALL VECMATH_FINLINE vec4d vd_insert_x(vec4d a, double x) { return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_replace_lane(VECMATH_WASM_V(a.xy), 0, x)), a.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_insert_y(vec4d a, double y) { return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_replace_lane(VECMATH_WASM_V(a.xy), 1, y)), a.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_insert_z(vec4d a, double z) { return vd_from_halves(a.xy, VECMATH_WASM_D(wasm_f64x2_replace_lane(VECMATH_WASM_V(a.zw), 0, z))); } +VECTORCALL VECMATH_FINLINE vec4d vd_insert_w(vec4d a, double w) { return vd_from_halves(a.xy, VECMATH_WASM_D(wasm_f64x2_replace_lane(VECMATH_WASM_V(a.zw), 1, w))); } + +VECTORCALL VECMATH_FINLINE vec4d vd_ld(const double *m) { return vd_from_halves(VECMATH_WASM_D(wasm_v128_load(m)), VECMATH_WASM_D(wasm_v128_load(m + 2))); } +VECTORCALL VECMATH_FINLINE vec4d vd_ldu(const double *m) { return vd_ld(m); } +VECTORCALL VECMATH_FINLINE void vd_st(double *m, vec4d a) { wasm_v128_store(m, VECMATH_WASM_V(a.xy)); wasm_v128_store(m + 2, VECMATH_WASM_V(a.zw)); } +VECTORCALL VECMATH_FINLINE void vd_stu(double *m, vec4d a) { vd_st(m, a); } + +// DPoint3 layout: 3 packed doubles. _safe reads exactly 3 (.w = 0); store writes exactly 3. +VECTORCALL VECMATH_FINLINE vec4d vd_ldu_p3_safe(const double *m) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_v128_load(m)), VECMATH_WASM_D(wasm_v128_load64_zero(m + 2))); +} +VECTORCALL VECMATH_FINLINE void vd_stu_p3(double *p3, vec4d v) { wasm_v128_store(p3, VECMATH_WASM_V(v.xy)); wasm_v128_store64_lane(p3 + 2, VECMATH_WASM_V(v.zw), 0); } + +VECTORCALL VECMATH_FINLINE vec4d vd_cvt_from_vec4f(vec4f a) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_promote_low_f32x4(VECMATH_WASM_V(a))), + VECMATH_WASM_D(wasm_f64x2_promote_low_f32x4(VECMATH_WASM_V(v_perm_zwzw(a))))); +} +// demote_zero leaves (x, y, 0, 0); one shuffle joins the two halves +VECTORCALL VECMATH_FINLINE vec4f vd_cvt_to_vec4f(vec4d a) +{ + vec4f lo = VECMATH_WASM_F(wasm_f32x4_demote_f64x2_zero(VECMATH_WASM_V(a.xy))); + vec4f hi = VECMATH_WASM_F(wasm_f32x4_demote_f64x2_zero(VECMATH_WASM_V(a.zw))); + return v_perm_xyab(lo, hi); +} +VECTORCALL VECMATH_FINLINE vec4d vd_cvt_from_vec4i(vec4i a) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_convert_low_i32x4(VECMATH_WASM_V(a))), + VECMATH_WASM_D(wasm_f64x2_convert_low_i32x4(VECMATH_WASM_V(v_permi_zwzw(a))))); +} +// truncates toward zero, saturating like v_cvti_vec4i on this backend +VECTORCALL VECMATH_FINLINE vec4i vd_cvt_to_vec4i(vec4d a) +{ + vec4i lo = VECMATH_WASM_I(wasm_i32x4_trunc_sat_f64x2_zero(VECMATH_WASM_V(a.xy))); + vec4i hi = VECMATH_WASM_I(wasm_i32x4_trunc_sat_f64x2_zero(VECMATH_WASM_V(a.zw))); + return v_interleave_lo_i64(lo, hi); +} + +VECTORCALL VECMATH_FINLINE vec4d vd_add(vec4d a, vec4d b) { return vd_from_halves(a.xy + b.xy, a.zw + b.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_sub(vec4d a, vec4d b) { return vd_from_halves(a.xy - b.xy, a.zw - b.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_mul(vec4d a, vec4d b) { return vd_from_halves(a.xy * b.xy, a.zw * b.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_div(vec4d a, vec4d b) { return vd_from_halves(a.xy / b.xy, a.zw / b.zw); } +VECTORCALL VECMATH_FINLINE vec4d vd_neg(vec4d a) { return vd_from_halves(-a.xy, -a.zw); } +// pmin/pmax with swapped operands are exactly SSE minpd/maxpd (a < b ? a : b, b wins on NaN +// and on signed-zero ties), same reason as the float v_min/v_max +VECTORCALL VECMATH_FINLINE vec4d vd_min(vec4d a, vec4d b) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_pmin(VECMATH_WASM_V(b.xy), VECMATH_WASM_V(a.xy))), + VECMATH_WASM_D(wasm_f64x2_pmin(VECMATH_WASM_V(b.zw), VECMATH_WASM_V(a.zw)))); +} +VECTORCALL VECMATH_FINLINE vec4d vd_max(vec4d a, vec4d b) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_pmax(VECMATH_WASM_V(b.xy), VECMATH_WASM_V(a.xy))), + VECMATH_WASM_D(wasm_f64x2_pmax(VECMATH_WASM_V(b.zw), VECMATH_WASM_V(a.zw)))); +} +VECTORCALL VECMATH_FINLINE vec4d vd_sqrt(vec4d a) +{ + return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_sqrt(VECMATH_WASM_V(a.xy))), VECMATH_WASM_D(wasm_f64x2_sqrt(VECMATH_WASM_V(a.zw)))); +} +VECTORCALL VECMATH_FINLINE vec4d vd_sqrt_x(vec4d a) +{ + vecmath_f64x2 s = VECMATH_WASM_D(wasm_f64x2_sqrt(VECMATH_WASM_V(a.xy))); + return vd_from_halves(__builtin_shufflevector(s, a.xy, 0, 3), a.zw); // .y kept, as on SSE +} + +// Left to right ((x+y)+z)+w, matching scalar association; see the x86 note above. +VECTORCALL VECMATH_FINLINE vec4d vd_hadd4_x(vec4d a) +{ + vecmath_f64x2 s = a.xy + __builtin_shufflevector(a.xy, a.xy, 1, 0); // x+y in both lanes + s = s + __builtin_shufflevector(a.zw, a.zw, 0, 0); // +z + s = s + __builtin_shufflevector(a.zw, a.zw, 1, 1); // +w + return vd_from_halves(s, s); +} +VECTORCALL VECMATH_FINLINE vec4d vd_hadd4(vec4d a) { return vd_hadd4_x(a); } +VECTORCALL VECMATH_FINLINE vec4d vd_hadd3_x(vec4d a) +{ + vecmath_f64x2 s = a.xy + __builtin_shufflevector(a.xy, a.xy, 1, 0); // x+y in both lanes + s = s + __builtin_shufflevector(a.zw, a.zw, 0, 0); // +z + return vd_from_halves(s, s); +} +VECTORCALL VECMATH_FINLINE vec4d vd_hadd3(vec4d a) { return vd_hadd3_x(a); } + +VECTORCALL VECMATH_FINLINE vec4d vd_dot4(vec4d a, vec4d b) { return vd_hadd4(vd_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4d vd_dot4_x(vec4d a, vec4d b) { return vd_hadd4_x(vd_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4d vd_dot3(vec4d a, vec4d b) { return vd_hadd3(vd_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4d vd_dot3_x(vec4d a, vec4d b) { return vd_hadd3_x(vd_mul(a, b)); } + +// r.x = ay*bz - az*by, r.y = az*bx - ax*bz, r.z = ax*by - ay*bx; .w unspecified +VECTORCALL VECMATH_FINLINE vec4d vd_cross3(vec4d a, vec4d b) +{ + vecmath_f64x2 ayz = __builtin_shufflevector(a.xy, a.zw, 1, 2), azx = __builtin_shufflevector(a.zw, a.xy, 0, 2); + vecmath_f64x2 bzx = __builtin_shufflevector(b.zw, b.xy, 0, 2), byz = __builtin_shufflevector(b.xy, b.zw, 1, 2); + vecmath_f64x2 r_lo = ayz * bzx - azx * byz; + vecmath_f64x2 r_hi = a.xy * __builtin_shufflevector(b.xy, b.xy, 1, 0) - __builtin_shufflevector(a.xy, a.xy, 1, 0) * b.xy; + return vd_from_halves(r_lo, r_hi); +} + #elif _TARGET_SIMD_SCALAR // ------------------------------------------------------------------------------------------------ // scalar per-lane (see dag_vecMath_scalar.h for the contract this follows) diff --git a/include/vecmath/dag_vecMath_wasm.h b/include/vecmath/dag_vecMath_wasm.h new file mode 100644 index 0000000000..104d7ae235 --- /dev/null +++ b/include/vecmath/dag_vecMath_wasm.h @@ -0,0 +1,642 @@ +// +// Dagor Engine 6.5 - 1st party libs +// Copyright (C) Gaijin Games KFT. All rights reserved. +// +#pragma once + +// WebAssembly SIMD128 backend: implements the same primitive contract as dag_vecMath_pc_sse.h / +// dag_vecMath_neon.h on the fixed-width wasm SIMD proposal (clang -msimd128). Every two-source +// lane permutation is one i8x16.shuffle, so the v_perm_* family is written as plain +// __builtin_shufflevector. Semantics follow the SSE backend where the ISA allows it at no cost: +// v_min/v_max are pmin/pmax with swapped operands (a < b ? a : b, the second operand wins on NaN +// and on signed-zero ties), v_sel/v_seli and the v_check_*/v_signmask family read only the lane +// sign bit, shift counts past the lane width zero-fill (sign-fill for v_srai). Float->int +// conversions saturate and map NaN to 0 (i32x4.trunc_sat), the NEON contract - SSE's INT_MIN +// answer has no single-instruction form here. There are no estimate instructions: the _est and +// _unprecise reciprocal/rsqrt forms are the exact division. v_madd/v_nmsub are the fused +// f32x4.relaxed_madd/nmadd when the translation unit is built with -mrelaxed-simd (the +// __wasm_relaxed_simd__ predefine) and VECMATH_NO_FMA is not defined; otherwise mul+add. + +#include +#include + +#define VECMATH_WASM_V(a) ((v128_t)(a)) +#define VECMATH_WASM_F(a) ((vec4f)(a)) +#define VECMATH_WASM_I(a) ((vec4i)(a)) +#if defined(__wasm_relaxed_simd__) && !defined(VECMATH_NO_FMA) + #define VECMATH_WASM_FMA 1 +#else + #define VECMATH_WASM_FMA 0 +#endif + +VECTORCALL VECMATH_FINLINE vec4f v_zero() { return VECMATH_WASM_F(wasm_f32x4_const_splat(0.f)); } +VECTORCALL VECMATH_FINLINE vec4i v_zeroi() { return VECMATH_WASM_I(wasm_i32x4_const_splat(0)); } +VECTORCALL VECMATH_FINLINE vec4f v_set_all_bits() { return VECMATH_WASM_F(wasm_i32x4_const_splat(-1)); } +VECTORCALL VECMATH_FINLINE vec4i v_set_all_bitsi() { return VECMATH_WASM_I(wasm_i32x4_const_splat(-1)); } +VECTORCALL VECMATH_FINLINE vec4f v_msbit() { return VECMATH_WASM_F(wasm_i32x4_const_splat((int32_t)0x80000000)); } +VECTORCALL VECMATH_FINLINE vec4f v_ld(const float *m) { return VECMATH_WASM_F(wasm_v128_load(m)); } +VECTORCALL VECMATH_FINLINE vec4f v_ldu(const float *m) { return VECMATH_WASM_F(wasm_v128_load(m)); } +VECTORCALL VECMATH_FINLINE void v_ld_soa2(const float *m, vec4f &x, vec4f &y) +{ + vec4f a = v_ld(m), b = v_ld(m + 4); + x = __builtin_shufflevector(a, b, 0, 2, 4, 6); + y = __builtin_shufflevector(a, b, 1, 3, 5, 7); +} +VECTORCALL VECMATH_FINLINE void v_ld_soa3(const float *m, vec4f &x, vec4f &y, vec4f &z) +{ + vec4f x0y0z0x1 = v_ld(m), y1z1x2y2 = v_ld(m + 4), z2x3y3z3 = v_ld(m + 8); + vec4f x2y2x3y3 = __builtin_shufflevector(y1z1x2y2, z2x3y3z3, 2, 3, 5, 6); + vec4f y0z0y1z1 = __builtin_shufflevector(x0y0z0x1, y1z1x2y2, 1, 2, 4, 5); + x = __builtin_shufflevector(x0y0z0x1, x2y2x3y3, 0, 3, 4, 6); + y = __builtin_shufflevector(y0z0y1z1, x2y2x3y3, 0, 2, 5, 7); + z = __builtin_shufflevector(y0z0y1z1, z2x3y3z3, 1, 3, 4, 7); +} +VECTORCALL VECMATH_FINLINE void v_ld_soa4(const float *m, vec4f &x, vec4f &y, vec4f &z, vec4f &w) +{ + vec4f a = v_ld(m), b = v_ld(m + 4), c = v_ld(m + 8), d = v_ld(m + 12); + vec4f t0 = __builtin_shufflevector(a, b, 0, 4, 1, 5), t1 = __builtin_shufflevector(a, b, 2, 6, 3, 7); + vec4f t2 = __builtin_shufflevector(c, d, 0, 4, 1, 5), t3 = __builtin_shufflevector(c, d, 2, 6, 3, 7); + x = __builtin_shufflevector(t0, t2, 0, 1, 4, 5); + y = __builtin_shufflevector(t0, t2, 2, 3, 6, 7); + z = __builtin_shufflevector(t1, t3, 0, 1, 4, 5); + w = __builtin_shufflevector(t1, t3, 2, 3, 6, 7); +} +// v128.load has no alignment requirement +VECTORCALL VECMATH_FINLINE void v_ldu_soa2(const float *m, vec4f &x, vec4f &y) { v_ld_soa2(m, x, y); } +VECTORCALL VECMATH_FINLINE void v_ldu_soa3(const float *m, vec4f &x, vec4f &y, vec4f &z) { v_ld_soa3(m, x, y, z); } +VECTORCALL VECMATH_FINLINE void v_ldu_soa4(const float *m, vec4f &x, vec4f &y, vec4f &z, vec4f &w) { v_ld_soa4(m, x, y, z, w); } + +VECTORCALL VECMATH_FINLINE void v_interleave3(vec4f x, vec4f y, vec4f z, vec4f &e0, vec4f &e1, vec4f &e2) +{ + vec4f x0y0x1x1 = __builtin_shufflevector(x, y, 0, 4, 1, 1); + vec4f y1z1y2y2 = __builtin_shufflevector(y, z, 1, 5, 2, 2); + vec4f z2x3z3z3 = __builtin_shufflevector(z, x, 2, 7, 3, 3); + e0 = __builtin_shufflevector(x0y0x1x1, z, 0, 1, 4, 2); // x0 y0 z0 x1 + e1 = __builtin_shufflevector(y1z1y2y2, x, 0, 1, 6, 2); // y1 z1 x2 y2 + e2 = __builtin_shufflevector(z2x3z3z3, y, 0, 1, 7, 2); // z2 x3 y3 z3 +} +VECTORCALL VECMATH_FINLINE void v_interleave4(vec4f x, vec4f y, vec4f z, vec4f w, vec4f &e0, vec4f &e1, vec4f &e2, vec4f &e3) +{ + vec4f t0 = __builtin_shufflevector(x, y, 0, 4, 1, 5), t1 = __builtin_shufflevector(x, y, 2, 6, 3, 7); + vec4f t2 = __builtin_shufflevector(z, w, 0, 4, 1, 5), t3 = __builtin_shufflevector(z, w, 2, 6, 3, 7); + e0 = __builtin_shufflevector(t0, t2, 0, 1, 4, 5); + e1 = __builtin_shufflevector(t0, t2, 2, 3, 6, 7); + e2 = __builtin_shufflevector(t1, t3, 0, 1, 4, 5); + e3 = __builtin_shufflevector(t1, t3, 2, 3, 6, 7); +} +VECTORCALL VECMATH_FINLINE void v_st_soa2(float *m, vec4f x, vec4f y) +{ + wasm_v128_store(m, VECMATH_WASM_V(__builtin_shufflevector(x, y, 0, 4, 1, 5))); + wasm_v128_store(m + 4, VECMATH_WASM_V(__builtin_shufflevector(x, y, 2, 6, 3, 7))); +} +VECTORCALL VECMATH_FINLINE void v_st_soa3(float *m, vec4f x, vec4f y, vec4f z) +{ + vec4f e0, e1, e2; + v_interleave3(x, y, z, e0, e1, e2); + wasm_v128_store(m, VECMATH_WASM_V(e0)); + wasm_v128_store(m + 4, VECMATH_WASM_V(e1)); + wasm_v128_store(m + 8, VECMATH_WASM_V(e2)); +} +VECTORCALL VECMATH_FINLINE void v_st_soa4(float *m, vec4f x, vec4f y, vec4f z, vec4f w) +{ + vec4f e0, e1, e2, e3; + v_interleave4(x, y, z, w, e0, e1, e2, e3); + wasm_v128_store(m, VECMATH_WASM_V(e0)); + wasm_v128_store(m + 4, VECMATH_WASM_V(e1)); + wasm_v128_store(m + 8, VECMATH_WASM_V(e2)); + wasm_v128_store(m + 12, VECMATH_WASM_V(e3)); +} +// v128.store has no alignment requirement +VECTORCALL VECMATH_FINLINE void v_stu_soa2(float *m, vec4f x, vec4f y) { v_st_soa2(m, x, y); } +VECTORCALL VECMATH_FINLINE void v_stu_soa3(float *m, vec4f x, vec4f y, vec4f z) { v_st_soa3(m, x, y, z); } +VECTORCALL VECMATH_FINLINE void v_stu_soa4(float *m, vec4f x, vec4f y, vec4f z, vec4f w) { v_st_soa4(m, x, y, z, w); } +VECTORCALL VECMATH_FINLINE vec4f v_ldu_x(const float *m) { return VECMATH_WASM_F(wasm_v128_load32_zero(m)); } // load x, zero others +VECTORCALL VECMATH_FINLINE vec4i v_ldi(const int *m) { return VECMATH_WASM_I(wasm_v128_load(m)); } +VECTORCALL VECMATH_FINLINE vec4i v_ldui(const int *m) { return VECMATH_WASM_I(wasm_v128_load(m)); } +VECTORCALL VECMATH_FINLINE vec4i v_ldush(const signed short *m) { return VECMATH_WASM_I(wasm_i32x4_load16x4(m)); } +VECTORCALL VECMATH_FINLINE vec4i v_lduush(const unsigned short *m) { return VECMATH_WASM_I(wasm_u32x4_load16x4(m)); } +VECTORCALL VECMATH_FINLINE vec3f v_ldu_p3_safe(const float *m) { return VECMATH_WASM_F(wasm_v128_load32_lane(m + 2, wasm_v128_load64_zero(m), 2)); } +VECTORCALL VECMATH_FINLINE vec4i v_ldui_p3_safe(const int *m) { return VECMATH_WASM_I(wasm_v128_load32_lane(m + 2, wasm_v128_load64_zero(m), 2)); } +VECTORCALL VECMATH_FINLINE vec4f v_splat_x(vec4f a) { return __builtin_shufflevector(a, a, 0, 0, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_splat_y(vec4f a) { return __builtin_shufflevector(a, a, 1, 1, 1, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_splat_z(vec4f a) { return __builtin_shufflevector(a, a, 2, 2, 2, 2); } +VECTORCALL VECMATH_FINLINE vec4f v_splat_w(vec4f a) { return __builtin_shufflevector(a, a, 3, 3, 3, 3); } +VECTORCALL VECMATH_FINLINE vec4i v_splat_xi(vec4i a) { return __builtin_shufflevector(a, a, 0, 0, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4i v_splat_yi(vec4i a) { return __builtin_shufflevector(a, a, 1, 1, 1, 1); } +VECTORCALL VECMATH_FINLINE vec4i v_splat_zi(vec4i a) { return __builtin_shufflevector(a, a, 2, 2, 2, 2); } +VECTORCALL VECMATH_FINLINE vec4i v_splat_wi(vec4i a) { return __builtin_shufflevector(a, a, 3, 3, 3, 3); } + +VECTORCALL VECMATH_FINLINE vec4f v_splats(float a) { return VECMATH_WASM_F(wasm_f32x4_splat(a)); } +VECTORCALL VECMATH_FINLINE vec4i v_splatsi(int a) { return VECMATH_WASM_I(wasm_i32x4_splat(a)); } +VECTORCALL VECMATH_FINLINE vec4i v_splatsi64(int64_t a) { return VECMATH_WASM_I(wasm_i64x2_splat(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_set_x(float a) { return VECMATH_WASM_F(wasm_f32x4_make(a, 0.f, 0.f, 0.f)); } // set x, zero others +VECTORCALL VECMATH_FINLINE vec4i v_seti_x(int a) { return VECMATH_WASM_I(wasm_i32x4_make(a, 0, 0, 0)); } // set x, zero others +VECTORCALL VECMATH_FINLINE vec4f v_make_vec4f(float x, float y, float z, float w) { return VECMATH_WASM_F(wasm_f32x4_make(x, y, z, w)); } +VECTORCALL VECMATH_FINLINE vec4i v_make_vec4i(int x, int y, int z, int w) { return VECMATH_WASM_I(wasm_i32x4_make(x, y, z, w)); } +VECTORCALL VECMATH_FINLINE vec4f v_make_vec3f(float x, float y, float z) { return v_make_vec4f(x, y, z, z); } +VECTORCALL VECMATH_FINLINE vec4i v_make_vec3i(int x, int y, int z) { return v_make_vec4i(x, y, z, z); } + +VECTORCALL VECMATH_FINLINE void v_st(void *m, vec4f v) { wasm_v128_store(m, VECMATH_WASM_V(v)); } +VECTORCALL VECMATH_FINLINE void v_stu(void *m, vec4f v) { wasm_v128_store(m, VECMATH_WASM_V(v)); } +VECTORCALL VECMATH_FINLINE void v_sti(void *m, vec4i v) { wasm_v128_store(m, VECMATH_WASM_V(v)); } +VECTORCALL VECMATH_FINLINE void v_stui(void *m, vec4i v) { wasm_v128_store(m, VECMATH_WASM_V(v)); } +VECTORCALL VECMATH_FINLINE void v_stui_half(void *m, vec4i v) { wasm_v128_store64_lane(m, VECMATH_WASM_V(v), 0); } +VECTORCALL VECMATH_FINLINE void v_stu_half(void *m, vec4f v) { wasm_v128_store64_lane(m, VECMATH_WASM_V(v), 0); } +VECTORCALL VECMATH_FINLINE void v_stu_p3(float *p3, vec3f v) { v_stu_half(p3, v); wasm_v128_store32_lane(p3 + 2, VECMATH_WASM_V(v), 2); } +VECTORCALL VECMATH_FINLINE void v_stui_p3(int *p3, vec4i v) { v_stui_half(p3, v); wasm_v128_store32_lane(p3 + 2, VECMATH_WASM_V(v), 2); } + +VECTORCALL VECMATH_FINLINE vec4f v_merge_hw(vec4f a, vec4f b) { return __builtin_shufflevector(a, b, 0, 4, 1, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_merge_lw(vec4f a, vec4f b) { return __builtin_shufflevector(a, b, 2, 6, 3, 7); } + +// i32x4.bitmask gathers the lane sign bits, the movmskps of this ISA +VECTORCALL VECMATH_FINLINE int v_signmask(vec4f a) { return (int)wasm_i32x4_bitmask(VECMATH_WASM_V(a)); } +VECTORCALL VECMATH_FINLINE int v_truemask(vec4f a) { return (int)wasm_i32x4_bitmask(VECMATH_WASM_V(a)); } +VECTORCALL VECMATH_FINLINE int v_count_true(vec4f a) { return __builtin_popcount(wasm_i32x4_bitmask(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE bool v_is_any_neg_b(vec4f a) { return wasm_i32x4_bitmask(VECMATH_WASM_V(a)) != 0; } + +VECTORCALL VECMATH_FINLINE int v_is_merge_planes_nout(vec4f m0, vec4f m1, vec4f m2, vec4f m3, vec4f m4, vec4f m5) +{ + // unsigned(-x) has bit 31 set iff x != 0, so the & chain needs no setcc per plane + unsigned nout = unsigned(-v_signmask(m0)) & unsigned(-v_signmask(m1)) & unsigned(-v_signmask(m2)) + & unsigned(-v_signmask(m3)) & unsigned(-v_signmask(m4)) & unsigned(-v_signmask(m5)); + return int(nout) >> 31; // arithmetic shift broadcasts bit 31: 0 or -1 +} + +VECTORCALL VECMATH_FINLINE vec4f v_min(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_pmin(VECMATH_WASM_V(b), VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_max(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_pmax(VECMATH_WASM_V(b), VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_maxi(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_max(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_mini(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_min(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_maxu(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_u32x4_max(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_minu(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_u32x4_min(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_add(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_add(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_sub(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_sub(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_mul(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_mul(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_addi(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_add(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_subi(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_sub(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_muli(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_mul(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } + +// pair ops fold (x,y),(z,w) of a into .xy and of b into .zw, the SSE haddps lane order +VECTORCALL VECMATH_FINLINE vec4f v_min_pairs(vec4f a, vec4f b) +{ return v_min(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4f v_max_pairs(vec4f a, vec4f b) +{ return v_max(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4f v_add_pairs(vec4f a, vec4f b) +{ return v_add(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4i v_addi_pairs(vec4i a, vec4i b) +{ return v_addi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4i v_mini_pairs(vec4i a, vec4i b) +{ return v_mini(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4i v_maxi_pairs(vec4i a, vec4i b) +{ return v_maxi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } + +VECTORCALL VECMATH_FINLINE bool v_test_all_bits_zeros(vec4f a) { return !wasm_v128_any_true(VECMATH_WASM_V(a)); } +VECTORCALL VECMATH_FINLINE bool v_test_all_bits_ones(vec4f a) { return !wasm_v128_any_true(wasm_v128_not(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE bool v_test_any_bit_set(vec4f a) { return wasm_v128_any_true(VECMATH_WASM_V(a)); } + +VECTORCALL VECMATH_FINLINE bool v_check_xyzw_all_true(vec4f a) { return v_signmask(a) == 0b1111; } +VECTORCALL VECMATH_FINLINE bool v_check_xyzw_all_false(vec4f a) { return v_signmask(a) == 0; } +VECTORCALL VECMATH_FINLINE bool v_check_xyzw_any_true(vec4f a) { return v_signmask(a) != 0; } +VECTORCALL VECMATH_FINLINE bool v_check_xyz_all_true(vec4f a) { return (v_signmask(a) & 0b111) == 0b111; } +VECTORCALL VECMATH_FINLINE bool v_check_xyz_all_false(vec4f a) { return (v_signmask(a) & 0b111) == 0; } +VECTORCALL VECMATH_FINLINE bool v_check_xyz_any_true(vec4f a) { return (v_signmask(a) & 0b111) != 0; } + +VECTORCALL VECMATH_FINLINE vec4f v_cmp_eq(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_eq(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_neq(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_ne(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_eqi(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_i32x4_eq(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_cmp_eqi(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_eq(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_ge(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_ge(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_gt(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_gt(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_cmp_lti(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_lt(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_cmp_gti(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_gt(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } + +VECTORCALL VECMATH_FINLINE vec4f is_neg_special(vec4f a) +{ + vec4f msbit = v_msbit(); + return v_cmp_eqi(VECMATH_WASM_F(wasm_v128_and(VECMATH_WASM_V(a), VECMATH_WASM_V(msbit))), msbit); +} + +VECTORCALL VECMATH_FINLINE vec4f v_and(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_and(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_andnot(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_andnot(VECMATH_WASM_V(b), VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_or(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_or(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_xor(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_xor(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_not(vec4f a) { return VECMATH_WASM_F(wasm_v128_not(VECMATH_WASM_V(a))); } +// v128.bitselect selects per bit; the arithmetic shift widens the sign bit into the lane first +VECTORCALL VECMATH_FINLINE vec4f v_sel(vec4f a, vec4f b, vec4f c) +{ + return VECMATH_WASM_F(wasm_v128_bitselect(VECMATH_WASM_V(b), VECMATH_WASM_V(a), wasm_i32x4_shr(VECMATH_WASM_V(c), 31))); +} +VECTORCALL VECMATH_FINLINE vec4i v_seli(vec4i a, vec4i b, vec4i c) +{ + return VECMATH_WASM_I(wasm_v128_bitselect(VECMATH_WASM_V(b), VECMATH_WASM_V(a), wasm_i32x4_shr(VECMATH_WASM_V(c), 31))); +} +VECTORCALL VECMATH_FINLINE vec4f v_btsel(vec4f a, vec4f b, vec4f c) { return VECMATH_WASM_F(wasm_v128_bitselect(VECMATH_WASM_V(b), VECMATH_WASM_V(a), VECMATH_WASM_V(c))); } +VECTORCALL VECMATH_FINLINE vec4i v_btseli(vec4i a, vec4i b, vec4i c) { return VECMATH_WASM_I(wasm_v128_bitselect(VECMATH_WASM_V(b), VECMATH_WASM_V(a), VECMATH_WASM_V(c))); } + +VECTORCALL VECMATH_FINLINE vec4i v_cvti_vec4i(vec4f a) { return VECMATH_WASM_I(wasm_i32x4_trunc_sat_f32x4(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_cvtu_vec4i_ieee(vec4f a) { return VECMATH_WASM_I(wasm_u32x4_trunc_sat_f32x4(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_cvtu_vec4i(vec4f a) { return v_cvtu_vec4i_ieee(a); } +VECTORCALL VECMATH_FINLINE vec4f v_cvtu_vec4f_ieee(vec4i a) { return VECMATH_WASM_F(wasm_f32x4_convert_u32x4(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_cvtu_vec4f(vec4i a) { return v_cvtu_vec4f_ieee(a); } +VECTORCALL VECMATH_FINLINE vec4f v_cvti_vec4f(vec4i a) { return VECMATH_WASM_F(wasm_f32x4_convert_i32x4(VECMATH_WASM_V(a))); } + +VECTORCALL VECMATH_FINLINE vec4i v_cast_vec4i(vec4f a) { return VECMATH_WASM_I(a); } +VECTORCALL VECMATH_FINLINE vec4f v_cast_vec4f(vec4i a) { return VECMATH_WASM_F(a); } + +VECTORCALL VECMATH_FINLINE vec4f v_floor(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_floor(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_ceil(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_ceil(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_trunc(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_trunc(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_round_ieee(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_nearest(VECMATH_WASM_V(a))); } +// f32x4.nearest is ties-to-even; ties away from zero is decided on the truncated remainder, the +// SSE form: trunc and a - trunc(a) are both exact, unlike biasing a by a signed half first +VECTORCALL VECMATH_FINLINE vec4f v_round(vec4f a) +{ + vec4f t = v_trunc(a); + vec4f sign = v_and(a, v_cast_vec4f(V_CI_SIGN_MASK)); + vec4f absFrac = v_xor(v_sub(a, t), sign); // truncation keeps the remainder on a's side of zero + vec4f away = v_cmp_ge(absFrac, V_C_HALF); + return v_add(t, v_or(v_and(away, V_C_ONE), sign)); +} +VECTORCALL VECMATH_FINLINE vec4i v_cvt_roundi(vec4f a) { return v_cvti_vec4i(v_round(a)); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_roundi_ieee(vec4f a) { return v_cvti_vec4i(v_round_ieee(a)); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_trunci(vec4f a) { return v_cvti_vec4i(a); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_floori(vec4f a) { return v_cvti_vec4i(v_floor(a)); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_ceili(vec4f a) { return v_cvti_vec4i(v_ceil(a)); } + +VECTORCALL VECMATH_FINLINE vec4f sse4_floor(vec4f a) { return v_floor(a); } +VECTORCALL VECMATH_FINLINE vec4f sse4_ceil(vec4f a) { return v_ceil(a); } +VECTORCALL VECMATH_FINLINE vec4f sse4_round(vec4f a) { return v_round(a); } +VECTORCALL VECMATH_FINLINE vec4i sse4_cvt_floori(vec4f a) { return v_cvt_floori(a); } +VECTORCALL VECMATH_FINLINE vec4i sse4_cvt_ceili(vec4f a) { return v_cvt_ceili(a); } +VECTORCALL VECMATH_FINLINE vec4i sse4_cvt_trunci(vec4f a) { return v_cvt_trunci(a); } + +#if VECMATH_WASM_FMA +VECTORCALL VECMATH_FINLINE vec4f v_madd(vec4f a, vec4f b, vec4f c) { return VECMATH_WASM_F(wasm_f32x4_relaxed_madd(VECMATH_WASM_V(a), VECMATH_WASM_V(b), VECMATH_WASM_V(c))); } +VECTORCALL VECMATH_FINLINE vec4f v_nmsub(vec4f a, vec4f b, vec4f c) { return VECMATH_WASM_F(wasm_f32x4_relaxed_nmadd(VECMATH_WASM_V(a), VECMATH_WASM_V(b), VECMATH_WASM_V(c))); } +#else +VECTORCALL VECMATH_FINLINE vec4f v_madd(vec4f a, vec4f b, vec4f c) { return v_add(v_mul(a, b), c); } +VECTORCALL VECMATH_FINLINE vec4f v_nmsub(vec4f a, vec4f b, vec4f c) { return v_sub(c, v_mul(a, b)); } +#endif +VECTORCALL VECMATH_FINLINE vec4f v_msub(vec4f a, vec4f b, vec4f c) { return v_sub(v_mul(a, b), c); } +// there is no scalar-lane form: the _x variants are the packed ops, .yzw hold the packed result +VECTORCALL VECMATH_FINLINE vec4f v_add_x(vec4f a, vec4f b) { return v_add(a, b); } +VECTORCALL VECMATH_FINLINE vec4f v_sub_x(vec4f a, vec4f b) { return v_sub(a, b); } +VECTORCALL VECMATH_FINLINE vec4f v_mul_x(vec4f a, vec4f b) { return v_mul(a, b); } +VECTORCALL VECMATH_FINLINE vec4f v_madd_x(vec4f a, vec4f b, vec4f c) { return v_madd(a, b, c); } +VECTORCALL VECMATH_FINLINE vec4f v_msub_x(vec4f a, vec4f b, vec4f c) { return v_msub(a, b, c); } +VECTORCALL VECMATH_FINLINE vec4f v_nmsub_x(vec4f a, vec4f b, vec4f c) { return v_nmsub(a, b, c); } + +VECTORCALL VECMATH_FINLINE vec4i v_addi16(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i16x8_add(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_subi16(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i16x8_sub(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_muli16(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i16x8_mul(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_mulhi16(vec4i a, vec4i b) +{ + v128_t lo = wasm_i32x4_extmul_low_i16x8(VECMATH_WASM_V(a), VECMATH_WASM_V(b)); + v128_t hi = wasm_i32x4_extmul_high_i16x8(VECMATH_WASM_V(a), VECMATH_WASM_V(b)); + return VECMATH_WASM_I(wasm_i16x8_narrow_i32x4(wasm_i32x4_shr(lo, 16), wasm_i32x4_shr(hi, 16))); +} +// i32x4.dot_i16x8 is pmaddwd: pairwise products summed into the 32-bit lane, wrapping +VECTORCALL VECMATH_FINLINE vec4i v_madd_i16(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_dot_i16x8(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_splatsi16(int v) { return VECMATH_WASM_I(wasm_i16x8_splat((int16_t)v)); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_lo_i8(vec4i a, vec4i b) +{ return VECMATH_WASM_I(wasm_i8x16_shuffle(VECMATH_WASM_V(a), VECMATH_WASM_V(b), 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23)); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_hi_i8(vec4i a, vec4i b) +{ return VECMATH_WASM_I(wasm_i8x16_shuffle(VECMATH_WASM_V(a), VECMATH_WASM_V(b), 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31)); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_lo_i16(vec4i a, vec4i b) +{ return VECMATH_WASM_I(wasm_i16x8_shuffle(VECMATH_WASM_V(a), VECMATH_WASM_V(b), 0, 8, 1, 9, 2, 10, 3, 11)); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_hi_i16(vec4i a, vec4i b) +{ return VECMATH_WASM_I(wasm_i16x8_shuffle(VECMATH_WASM_V(a), VECMATH_WASM_V(b), 4, 12, 5, 13, 6, 14, 7, 15)); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_lo_i32(vec4i a, vec4i b) { return __builtin_shufflevector(a, b, 0, 4, 1, 5); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_hi_i32(vec4i a, vec4i b) { return __builtin_shufflevector(a, b, 2, 6, 3, 7); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_lo_i64(vec4i a, vec4i b) { return __builtin_shufflevector(a, b, 0, 1, 4, 5); } +VECTORCALL VECMATH_FINLINE vec4i v_interleave_hi_i64(vec4i a, vec4i b) { return __builtin_shufflevector(a, b, 2, 3, 6, 7); } +VECTORCALL VECMATH_FINLINE vec4i v_perm_i8(vec4i t, vec4i k) +{ + // match pshufb exactly: index bits 4..6 are ignored, bit 7 zeroes the lane + // (i8x16.swizzle returns 0 for any index >= 16) + v128_t ki = wasm_v128_and(VECMATH_WASM_V(k), wasm_u8x16_const_splat(0x8F)); + return VECMATH_WASM_I(wasm_i8x16_swizzle(VECMATH_WASM_V(t), ki)); +} +VECTORCALL VECMATH_FINLINE vec4i v_cmp_eqi8(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i8x16_eq(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } + +VECTORCALL VECMATH_FINLINE vec4f v_hadd4_x(vec4f a) +{ + vec4f s = v_add(a, __builtin_shufflevector(a, a, 2, 3, 0, 1)); // x+z, y+w, z+x, w+y + return v_add(s, __builtin_shufflevector(s, s, 1, 0, 3, 2)); // all lanes +} +VECTORCALL VECMATH_FINLINE vec4f v_hadd3_x(vec4f a) +{ + vec4f s = v_add(a, v_splat_y(a)); + return v_add(s, v_splat_z(a)); +} + +VECTORCALL VECMATH_FINLINE vec4f v_rot_1(vec4f a) { return __builtin_shufflevector(a, a, 1, 2, 3, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_rot_2(vec4f a) { return __builtin_shufflevector(a, a, 2, 3, 0, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_rot_3(vec4f a) { return __builtin_shufflevector(a, a, 3, 0, 1, 2); } +VECTORCALL VECMATH_FINLINE vec4i v_roti_1(vec4i a) { return __builtin_shufflevector(a, a, 1, 2, 3, 0); } +VECTORCALL VECMATH_FINLINE vec4i v_roti_2(vec4i a) { return __builtin_shufflevector(a, a, 2, 3, 0, 1); } +VECTORCALL VECMATH_FINLINE vec4i v_roti_3(vec4i a) { return __builtin_shufflevector(a, a, 3, 0, 1, 2); } + +VECTORCALL VECMATH_FINLINE vec4f v_hmin(vec4f a) +{ + a = v_min(a, v_rot_1(a)); + return v_min(a, v_rot_2(a)); +} +VECTORCALL VECMATH_FINLINE vec4f v_hmax(vec4f a) +{ + a = v_max(a, v_rot_1(a)); + return v_max(a, v_rot_2(a)); +} +VECTORCALL VECMATH_FINLINE vec4f v_hmin3(vec3f a) { return v_min(v_splat_x(a), v_min(v_splat_y(a), v_splat_z(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_hmax3(vec3f a) { return v_max(v_splat_x(a), v_max(v_splat_y(a), v_splat_z(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_hmini(vec4i a) +{ + a = v_mini(a, v_roti_1(a)); + return v_mini(a, v_roti_2(a)); +} +VECTORCALL VECMATH_FINLINE vec4i v_hmaxi(vec4i a) +{ + a = v_maxi(a, v_roti_1(a)); + return v_maxi(a, v_roti_2(a)); +} +VECTORCALL VECMATH_FINLINE vec4i v_hmini3(vec4i a) { return v_mini(v_splat_xi(a), v_mini(v_splat_yi(a), v_splat_zi(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_hmaxi3(vec4i a) { return v_maxi(v_splat_xi(a), v_maxi(v_splat_yi(a), v_splat_zi(a))); } + +VECTORCALL VECMATH_FINLINE vec4f v_div(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_f32x4_div(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4f v_div_x(vec4f a, vec4f b) { return v_div(a, b); } +VECTORCALL VECMATH_FINLINE vec4f v_sqrt(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_sqrt(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_sqrt_x(vec4f a) { return v_sqrt(a); } +// no estimate instructions in the ISA: every reciprocal form is the exact division +VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise(vec4f a) { return v_div(V_C_ONE, a); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_est(vec4f a) { return v_div(V_C_ONE, a); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise_x(vec4f a) { return v_div(V_C_ONE, a); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_est_x(vec4f a) { return v_div(V_C_ONE, a); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise_x(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est_x(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } + +VECTORCALL VECMATH_FINLINE vec4f v_neg(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_neg(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_negi(vec4i a) { return VECMATH_WASM_I(wasm_i32x4_neg(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_abs(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_abs(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_absi(vec4i a) { return VECMATH_WASM_I(wasm_i32x4_abs(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4f v_abs_diff(vec4f a, vec4f b) { return v_abs(v_sub(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_abs_ge(vec4f a, vec4f b) { return v_cmp_ge(v_abs(a), v_abs(b)); } +VECTORCALL VECMATH_FINLINE vec4f v_cmp_abs_gt(vec4f a, vec4f b) { return v_cmp_gt(v_abs(a), v_abs(b)); } + +// every two-source lane permutation is one i8x16.shuffle +VECTORCALL VECMATH_FINLINE vec4f v_perm_xxyy(vec4f v) { return __builtin_shufflevector(v, v, 0, 0, 1, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xxzz(vec4f v) { return __builtin_shufflevector(v, v, 0, 0, 2, 2); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xyxy(vec4f v) { return __builtin_shufflevector(v, v, 0, 1, 0, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xyzz(vec4f v) { return __builtin_shufflevector(v, v, 0, 1, 2, 2); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xzxz(vec4f v) { return __builtin_shufflevector(v, v, 0, 2, 0, 2); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_ywyw(vec4f v) { return __builtin_shufflevector(v, v, 1, 3, 1, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yxwz(vec4f v) { return __builtin_shufflevector(v, v, 1, 0, 3, 2); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yyww(vec4f v) { return __builtin_shufflevector(v, v, 1, 1, 3, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zwzw(vec4f v) { return __builtin_shufflevector(v, v, 2, 3, 2, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zzww(vec4f v) { return __builtin_shufflevector(v, v, 2, 2, 3, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xycd(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 1, 6, 7); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zwcd(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 2, 3, 6, 7); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xyab(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 1, 4, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_ayzw(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 4, 1, 2, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xbzw(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 5, 2, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xycw(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 1, 6, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xyzd(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 1, 2, 7); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xzac(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 2, 4, 6); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_ywbd(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 3, 5, 7); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xazc(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 4, 2, 6); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_ybwd(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 5, 3, 7); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yzwa(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 2, 3, 4); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zwab(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 2, 3, 4, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_wabc(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 3, 4, 5, 6); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yzxy(vec4f v) { return __builtin_shufflevector(v, v, 1, 2, 0, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zxyw(vec4f a) { return __builtin_shufflevector(a, a, 2, 0, 1, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zxzx(vec4f v) { return __builtin_shufflevector(v, v, 2, 0, 2, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_wwyy(vec4f v) { return __builtin_shufflevector(v, v, 3, 3, 1, 1); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xaxa(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 4, 0, 4); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yybb(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 1, 5, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xxab(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 0, 4, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yzab(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 2, 4, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yzxw(vec4f v) { return __builtin_shufflevector(v, v, 1, 2, 0, 3); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yzxx(vec4f v) { return __builtin_shufflevector(v, v, 1, 2, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_bbyx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 5, 5, 1, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_bzxx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 5, 2, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_caxx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 6, 4, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xzbx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 2, 5, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_xzya(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 0, 2, 1, 4); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yaxx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 4, 0, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_yxxc(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 1, 0, 0, 6); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zxxb(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 2, 0, 0, 5); } +VECTORCALL VECMATH_FINLINE vec4f v_perm_zayx(vec4f xyzw, vec4f abcd) { return __builtin_shufflevector(xyzw, abcd, 2, 4, 1, 0); } +VECTORCALL VECMATH_FINLINE vec4f v_make_vec3f(vec4f x, vec4f y, vec4f z) +{ + return __builtin_shufflevector(__builtin_shufflevector(x, y, 0, 0, 4, 4), z, 0, 2, 4, 4); +} + +// integer single-source perms: the shuffle is typeless, the float forms compile identically +VECTORCALL VECMATH_FINLINE vec4i v_permi_xzxz(vec4i xyzw) { return v_cast_vec4i(v_perm_xzxz(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_ywyw(vec4i xyzw) { return v_cast_vec4i(v_perm_ywyw(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_xyxy(vec4i xyzw) { return v_cast_vec4i(v_perm_xyxy(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_zwzw(vec4i xyzw) { return v_cast_vec4i(v_perm_zwzw(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_xxyy(vec4i xyzw) { return v_cast_vec4i(v_perm_xxyy(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_zzww(vec4i xyzw) { return v_cast_vec4i(v_perm_zzww(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_xxzz(vec4i xyzw) { return v_cast_vec4i(v_perm_xxzz(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_yyww(vec4i xyzw) { return v_cast_vec4i(v_perm_yyww(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_wwyy(vec4i xyzw) { return v_cast_vec4i(v_perm_wwyy(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_yzxw(vec4i xyzw) { return v_cast_vec4i(v_perm_yzxw(v_cast_vec4f(xyzw))); } +VECTORCALL VECMATH_FINLINE vec4i v_permi_yzxy(vec4i xyzw) { return v_cast_vec4i(v_perm_yzxy(v_cast_vec4f(xyzw))); } + +VECTORCALL VECMATH_FINLINE vec3f v_mat43_extract_pos(mat43f_cref mat) +{ + vec4f xyjj = __builtin_shufflevector(mat.row0, mat.row1, 3, 7, 0, 0); + return __builtin_shufflevector(xyjj, mat.row2, 0, 1, 7, 0); +} + +VECTORCALL VECMATH_FINLINE vec4f v_dot2_x(vec4f a, vec4f b) +{ + vec4f m = v_mul(a, b); + return v_add(m, v_splat_y(m)); +} +VECTORCALL VECMATH_FINLINE vec4f v_dot2(vec4f a, vec4f b) { return v_splat_x(v_dot2_x(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_dot3_x(vec4f a, vec4f b) { return v_hadd3_x(v_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_dot3(vec4f a, vec4f b) { return v_splat_x(v_dot3_x(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_dot4_x(vec4f a, vec4f b) { return v_hadd4_x(v_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_dot4(vec4f a, vec4f b) { return v_hadd4_x(v_mul(a, b)); } + +VECTORCALL VECMATH_FINLINE vec3f v_cross3(vec3f a, vec3f b) +{ + // (a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) + vec3f ayzx = v_perm_yzxy(a); + vec3f byzx = v_perm_yzxy(b); + return v_perm_yzxy(v_sub(v_mul(a, byzx), v_mul(ayzx, b))); +} + +// v_length*_sq and v_norm2/3/4 live in dag_vecMath_common.h (portable form). + +VECTORCALL VECMATH_FINLINE vec4f v_plane_dist_x(plane3f a, vec3f b) { return v_add_x(v_dot3_x(a, b), v_rot_3(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_plane_dist(plane3f a, vec3f b) { return v_splat_x(v_plane_dist_x(a, b)); } + +VECTORCALL VECMATH_FINLINE void v_mat_33cu_from_mat33(float * __restrict m33, const mat33f& tm) +{ + vec4f v0 = __builtin_shufflevector(tm.col0, tm.col1, 0, 1, 2, 4); + vec4f v1 = __builtin_shufflevector(tm.col1, tm.col2, 1, 2, 4, 5); + v_stu(m33 + 0, v0); + v_stu(m33 + 4, v1); + m33[8] = v_extract_z(tm.col2); +} + +VECTORCALL VECMATH_FINLINE void v_mat_43cu_from_mat44(float * __restrict m43, const mat44f &tm) +{ + vec4f v0 = __builtin_shufflevector(tm.col0, tm.col1, 0, 1, 2, 4); + vec4f v1 = __builtin_shufflevector(tm.col1, tm.col2, 1, 2, 4, 5); + vec4f v2 = __builtin_shufflevector(tm.col2, tm.col3, 2, 4, 5, 6); + v_stu(m43 + 0, v0); + v_stu(m43 + 4, v1); + v_stu(m43 + 8, v2); +} + +VECTORCALL VECMATH_FINLINE void v_mat_43ca_from_mat44(float * __restrict m43, const mat44f &tm) +{ + v_mat_43cu_from_mat44(m43, tm); +} + +// mat44f from unaligned TMatrix +VECTORCALL VECMATH_FINLINE void v_mat44_make_from_43cu_unsafe(mat44f &tmV, const float *const __restrict m43) +{ + vec4f v0 = v_ldu(m43 + 0); + vec4f v1 = v_ldu(m43 + 4); + vec4f v2 = v_ldu(m43 + 8); + + tmV.col0 = v0; + tmV.col1 = __builtin_shufflevector(v0, v1, 3, 4, 5, 6); + tmV.col2 = __builtin_shufflevector(v1, v2, 2, 3, 4, 5); + tmV.col3 = __builtin_shufflevector(v2, v2, 1, 2, 3, 0); +} + +VECTORCALL VECMATH_FINLINE void v_mat44_make_from_43cu(mat44f &tmV, const float *const __restrict m43) +{ + v_mat44_make_from_43cu_unsafe(tmV, m43); + v_mat44_make_affine(tmV); +} + +VECTORCALL VECMATH_FINLINE void v_mat44_make_from_43ca(mat44f &tmV, const float *const __restrict m43) +{ + v_mat44_make_from_43cu(tmV, m43); +} + +VECTORCALL VECMATH_FINLINE void v_mat43_make_from_43cu_unsafe(mat43f &tmV, const float *const __restrict m43) +{ + v_ldu_soa3(m43, tmV.row0, tmV.row1, tmV.row2); +} + +VECTORCALL VECMATH_FINLINE void v_mat44_ident(mat44f &dest) +{ + dest.col3 = V_C_UNIT_0001; + dest.col2 = v_rot_1(dest.col3); + dest.col1 = v_rot_1(dest.col2); + dest.col0 = v_rot_1(dest.col1); +} +VECTORCALL VECMATH_FINLINE void v_mat44_ident_swapxz(mat44f &dest) +{ + dest.col3 = V_C_UNIT_0001; + dest.col0 = v_rot_1(dest.col3); + dest.col1 = v_rot_1(dest.col0); + dest.col2 = v_rot_1(dest.col1); +} +VECTORCALL VECMATH_FINLINE void v_mat33_ident(mat33f &dest) +{ + dest.col2 = V_C_UNIT_0010; + dest.col1 = v_rot_1(dest.col2); + dest.col0 = v_rot_1(dest.col1); +} +VECTORCALL VECMATH_FINLINE void v_mat33_ident_swapxz(mat33f &dest) +{ + dest.col0 = V_C_UNIT_0010; + dest.col1 = v_rot_1(dest.col0); + dest.col2 = v_rot_1(dest.col1); +} + +// v_mat44_transpose*, v_mat43_transpose_to_mat44, v_mat44_transpose_to_mat43, +// v_mat44/33_mul_vec*, v_mat33_inverse and v_mat44_det live in dag_vecMath_common.h. + +VECTORCALL VECMATH_FINLINE short v_extract_xi16(vec4i v) { return (short)wasm_i16x8_extract_lane(VECMATH_WASM_V(v), 0); } + +VECTORCALL VECMATH_FINLINE float v_extract_x(vec4f v) { return wasm_f32x4_extract_lane(VECMATH_WASM_V(v), 0); } +VECTORCALL VECMATH_FINLINE float v_extract_y(vec4f v) { return wasm_f32x4_extract_lane(VECMATH_WASM_V(v), 1); } +VECTORCALL VECMATH_FINLINE float v_extract_z(vec4f v) { return wasm_f32x4_extract_lane(VECMATH_WASM_V(v), 2); } +VECTORCALL VECMATH_FINLINE float v_extract_w(vec4f v) { return wasm_f32x4_extract_lane(VECMATH_WASM_V(v), 3); } + +VECTORCALL VECMATH_FINLINE int v_extract_xi(vec4i v) { return wasm_i32x4_extract_lane(VECMATH_WASM_V(v), 0); } +VECTORCALL VECMATH_FINLINE int v_extract_yi(vec4i v) { return wasm_i32x4_extract_lane(VECMATH_WASM_V(v), 1); } +VECTORCALL VECMATH_FINLINE int v_extract_zi(vec4i v) { return wasm_i32x4_extract_lane(VECMATH_WASM_V(v), 2); } +VECTORCALL VECMATH_FINLINE int v_extract_wi(vec4i v) { return wasm_i32x4_extract_lane(VECMATH_WASM_V(v), 3); } + +VECTORCALL VECMATH_FINLINE int64_t v_extract_xi64(vec4i v) { return wasm_i64x2_extract_lane(VECMATH_WASM_V(v), 0); } +VECTORCALL VECMATH_FINLINE int64_t v_extract_yi64(vec4i v) { return wasm_i64x2_extract_lane(VECMATH_WASM_V(v), 1); } + +VECTORCALL VECMATH_FINLINE int v_test_vec_x_eqi(vec3f v, vec3f a) { return v_extract_xi(v_cast_vec4i(v)) == v_extract_xi(v_cast_vec4i(a)) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_eqi_0(vec3f v) { return v_extract_xi(v_cast_vec4i(v)) == 0 ? 1 : 0; } + +VECTORCALL VECMATH_FINLINE int v_test_vec_x_eq(vec3f v, vec3f a) { return v_extract_x(v) == v_extract_x(a) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_gt(vec3f v, vec3f a) { return v_extract_x(v) > v_extract_x(a) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_ge(vec3f v, vec3f a) { return v_extract_x(v) >= v_extract_x(a) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_lt(vec3f v, vec3f a) { return v_extract_x(v) < v_extract_x(a) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_le(vec3f v, vec3f a) { return v_extract_x(v) <= v_extract_x(a) ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_eq_0(vec3f v) { return v_extract_x(v) == 0.f ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_gt_0(vec3f v) { return v_extract_x(v) > 0.f ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_ge_0(vec3f v) { return v_extract_x(v) >= 0.f ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_lt_0(vec3f v) { return v_extract_x(v) < 0.f ? 1 : 0; } +VECTORCALL VECMATH_FINLINE int v_test_vec_x_le_0(vec3f v) { return v_extract_x(v) <= 0.f ? 1 : 0; } + +VECTORCALL VECMATH_FINLINE vec4i v_ldui_half(const void *m) { return VECMATH_WASM_I(wasm_v128_load64_zero(m)); } +VECTORCALL VECMATH_FINLINE vec4f v_ldu_half(const void *m) { return VECMATH_WASM_F(wasm_v128_load64_zero(m)); } +VECMATH_FINLINE void v_prefetch(const void *m) { __builtin_prefetch(m); } + +VECTORCALL VECMATH_FINLINE vec4i v_cvt_lo_ush_vec4i(vec4i a) { return VECMATH_WASM_I(wasm_u32x4_extend_low_u16x8(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_hi_ush_vec4i(vec4i a) { return VECMATH_WASM_I(wasm_u32x4_extend_high_u16x8(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_lo_ssh_vec4i(vec4i a) { return VECMATH_WASM_I(wasm_i32x4_extend_low_i16x8(VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_cvt_hi_ssh_vec4i(vec4i a) { return VECMATH_WASM_I(wasm_i32x4_extend_high_i16x8(VECMATH_WASM_V(a))); } + +VECMATH_FINLINE vec4i v_cvt_byte_vec4i(uint32_t a) +{ + v128_t u8x16 = wasm_i32x4_make((int)a, 0, 0, 0); /* xxxx xxxx xxxx DCBA */ + v128_t u16x8 = wasm_u16x8_extend_low_u8x16(u8x16); /* 0x0x 0x0x 0D0C 0B0A */ + return VECMATH_WASM_I(wasm_u32x4_extend_low_u16x8(u16x8)); /* 000D 000C 000B 000A */ +} + +// i32x4.shl/shr take the count modulo the lane width; the guards keep the x86 answer for counts +// past it (zero, or the sign for sra) and fold away on an immediate count +VECTORCALL VECMATH_FINLINE vec4i v_slli(vec4i v, int bits) +{ return (unsigned)bits < 32u ? VECMATH_WASM_I(wasm_i32x4_shl(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } +VECTORCALL VECMATH_FINLINE vec4i v_srli(vec4i v, int bits) +{ return (unsigned)bits < 32u ? VECMATH_WASM_I(wasm_u32x4_shr(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } +VECTORCALL VECMATH_FINLINE vec4i v_srai(vec4i v, int bits) +{ return VECMATH_WASM_I(wasm_i32x4_shr(VECMATH_WASM_V(v), (unsigned)bits < 31u ? (uint32_t)bits : 31u)); } +VECTORCALL VECMATH_FINLINE vec4i v_slli_64(vec4i v, int bits) +{ return (unsigned)bits < 64u ? VECMATH_WASM_I(wasm_i64x2_shl(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } +VECTORCALL VECMATH_FINLINE vec4i v_srli_64(vec4i v, int bits) +{ return (unsigned)bits < 64u ? VECMATH_WASM_I(wasm_u64x2_shr(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } +VECTORCALL VECMATH_FINLINE vec4i v_slli_n(vec4i v, int bits) { return v_slli(v, bits); } +VECTORCALL VECMATH_FINLINE vec4i v_srli_n(vec4i v, int bits) { return v_srli(v, bits); } +VECTORCALL VECMATH_FINLINE vec4i v_srai_n(vec4i v, int bits) { return v_srai(v, bits); } +// the count rides in the low 64 bits of the vector, the psll/psrl/psra register form +VECTORCALL VECMATH_FINLINE vec4i v_slli_n(vec4i v, vec4i bits) { return v_slli(v, (int)v_extract_xi64(bits)); } +VECTORCALL VECMATH_FINLINE vec4i v_srli_n(vec4i v, vec4i bits) { return v_srli(v, (int)v_extract_xi64(bits)); } +VECTORCALL VECMATH_FINLINE vec4i v_srai_n(vec4i v, vec4i bits) { return v_srai(v, (int)v_extract_xi64(bits)); } +VECTORCALL VECMATH_FINLINE vec4i v_sll(vec4i v, int bits) { return v_slli(v, bits); } +VECTORCALL VECMATH_FINLINE vec4i v_srl(vec4i v, int bits) { return v_srli(v, bits); } +VECTORCALL VECMATH_FINLINE vec4i v_sra(vec4i v, int bits) { return v_srai(v, bits); } + +VECTORCALL VECMATH_FINLINE vec4i v_ori(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_v128_or(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_andi(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_v128_and(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_andnoti(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_v128_andnot(VECMATH_WASM_V(b), VECMATH_WASM_V(a))); } +VECTORCALL VECMATH_FINLINE vec4i v_xori(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_v128_xor(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } + +VECTORCALL VECMATH_FINLINE vec4i v_packs(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i16x8_narrow_i32x4(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_packs(vec4i a) { return v_packs(a, a); } +VECTORCALL VECMATH_FINLINE vec4i v_packus(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_u16x8_narrow_i32x4(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_packus(vec4i a) { return v_packus(a, a); } +VECTORCALL VECMATH_FINLINE vec4i v_packus16(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_u8x16_narrow_i16x8(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } +VECTORCALL VECMATH_FINLINE vec4i v_packus16(vec4i a) { return v_packus16(a, a); } + +// no half-precision conversion in the fixed-width proposal: the software half<->float path in +// dag_vecMath_common.h applies (_TARGET_HAS_FC16 stays undefined) diff --git a/include/vecmath/usage.md b/include/vecmath/usage.md index 0595164221..525761ccc9 100644 --- a/include/vecmath/usage.md +++ b/include/vecmath/usage.md @@ -1,7 +1,7 @@ # Using vecmath -Platform-abstracted SIMD vector math. Wraps SSE2/SSSE3/SSE4.1 (x86) and NEON (ARM) behind a -unified C API. Used pervasively throughout the Dagor Engine for all performance-critical math: +Platform-abstracted SIMD vector math. Wraps SSE2/SSSE3/SSE4.1 (x86), NEON (ARM), wasm SIMD128 +and a scalar per-lane fallback behind a unified C API. Used pervasively throughout the Dagor Engine for all performance-critical math: transforms, physics, BVH traversal, culling, animation. `vecmath/dag_vecMath.h` is the API reference: every `v_`-prefixed function is declared there with @@ -10,8 +10,8 @@ a comment. Grep it by prefix before writing anything by hand -- what you need pr v_triangle*). ## Key types -- `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON) -- `vec4i` -- 128-bit integer vector (__m128i / int32x4_t) +- `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a clang typed vector on wasm, a 16-byte struct on scalar) +- `vec4i` -- 128-bit integer vector (__m128i / int32x4_t / an int32 typed vector on wasm) - `mat33f` -- 3x3 column-major matrix (3 x vec3f) - `mat44f` -- 4x4 column-major matrix (4 x vec4f) - `mat43f` -- 4x3 row-major matrix (3 x vec4f, each row is xyzw where w = translation component) diff --git a/modules/dasImgui/CMakeLists.txt b/modules/dasImgui/CMakeLists.txt index a7ccce4457..fbe1862518 100644 --- a/modules/dasImgui/CMakeLists.txt +++ b/modules/dasImgui/CMakeLists.txt @@ -432,10 +432,17 @@ ELSEIF(EMSCRIPTEN) OPTION(DAS_IMGUI_WASM_PTHREADS "Build the wasm archives threaded (-pthread)" ON) MESSAGE(STATUS "dasImgui: emscripten/web build (3 module archives + FreeType; memory64=${DAS_IMGUI_WASM_MEMORY64} pthreads=${DAS_IMGUI_WASM_PTHREADS})") + # same SIMD feature set as web/CMakeLists.txt: vecmath's wasm backend selects itself on + # __wasm_simd128__, and the relaxed-SIMD knob mirrors DAS_WASM_RELAXED_SIMD so v_madd fuses + # (or not) on both sides of the archive boundary + OPTION(DAS_IMGUI_WASM_RELAXED_SIMD "Build the wasm archives with relaxed SIMD (matches DAS_WASM_RELAXED_SIMD)" ON) SET(IMGUI_WASM_FLAGS -fno-rtti - -msimd128 -msse2 -mnontrapping-fptoint + -msimd128 -mnontrapping-fptoint -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0) + IF(DAS_IMGUI_WASM_RELAXED_SIMD) + LIST(APPEND IMGUI_WASM_FLAGS -mrelaxed-simd) + ENDIF() TARGET_COMPILE_OPTIONS(freetype PRIVATE -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0) IF(DAS_IMGUI_WASM_MEMORY64) diff --git a/tests-cpp/big/vecmath_backend/CMakeLists.txt b/tests-cpp/big/vecmath_backend/CMakeLists.txt index c4ec38aa1e..4d96fb3930 100644 --- a/tests-cpp/big/vecmath_backend/CMakeLists.txt +++ b/tests-cpp/big/vecmath_backend/CMakeLists.txt @@ -32,3 +32,17 @@ if(NOT DAS_VECMATH_SCALAR) set_tests_properties(vecmath_native PROPERTIES LABELS "small") add_dependencies(test-small test_vecmath_native) endif() + +# Under emscripten (the web/ build) the native arm is the wasm SIMD128 backend and both arms +# become node-runnable .js + .wasm pairs under web/output/tests. The web scope's directory-wide +# link options embed daslib and disable main (-sINVOKE_RUN=0) for the interpreter binary; a +# backend test wants neither, so the pair keeps only the exception-model flags every object in +# that build carries. The wasm_build.yml lane builds and runs both under node. +if(EMSCRIPTEN) + target_compile_definitions(test_vecmath_native PRIVATE EXPECT_WASM=1) + foreach(_arm test_vecmath_scalar test_vecmath_native) + set_target_properties(${_arm} PROPERTIES + LINK_OPTIONS "-fwasm-exceptions;-sWASM_LEGACY_EXCEPTIONS=0" + RUNTIME_OUTPUT_DIRECTORY ${DAS_WEB_OUTPUT_DIR}/tests) + endforeach() +endif() diff --git a/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp b/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp index 51e6f05374..b4201ff214 100644 --- a/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp +++ b/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp @@ -3,7 +3,9 @@ // into a scalar-forced TU would mix two vec4f ABIs in one binary. Rows guarded // with !_TARGET_SIMD_NEON pin SSE-flavored semantics the scalar backend promises // to match (NaN/tie ordering, sign-bit select, out-of-range converts, shift -// counts past the lane width) - NEON diverges there by its own contract. +// counts past the lane width) - NEON diverges there by its own contract. The wasm +// backend keeps every SSE row but two it shares with NEON (VECMATH_TEST_PACKED_X_CVT +// below): _x forms are the packed op, and float->int converts saturate. #if defined(__FAST_MATH__) || defined(_M_FP_FAST) #error "the rows pin IEEE answers a fast-math build may fold; both arms are pinned to precise math in CMakeLists.txt" @@ -30,6 +32,12 @@ #if defined(EXPECT_NATIVE) && defined(_TARGET_SIMD_SCALAR) #error this target must select the native SIMD vecmath backend #endif +#if defined(EXPECT_WASM) && !defined(_TARGET_SIMD_WASM) +#error this target must select the wasm SIMD128 vecmath backend +#endif +#if defined(_TARGET_SIMD_NEON) || defined(_TARGET_SIMD_WASM) +#define VECMATH_TEST_PACKED_X_CVT 1 +#endif static int g_failed = 0; @@ -103,7 +111,7 @@ int main() check_int("add_x", (long long)f2u(v_extract_x(v_add_x(a, b))), (long long)f2u(3.5f)); check_int("nmsub_x", (long long)f2u(v_extract_x(v_nmsub_x(a, b, b))), (long long)f2u(-1.0f)); check_int("sqrt_x", (long long)f2u(v_extract_x(v_sqrt_x(v_make_vec4f(4.0f, 5.0f, 6.0f, 7.0f)))), (long long)f2u(2.0f)); -#if !defined(_TARGET_SIMD_NEON) +#if !defined(VECMATH_TEST_PACKED_X_CVT) check_lanes("add_x_keeps_yzw", v_add_x(a, b), f2u(3.5f), f2u(-2.25f), f2u(3.75f), f2u(-0.5f)); check_lanes("nmsub_x_keeps_c_yzw", v_nmsub_x(a, b, b), f2u(-1.0f), f2u(0.5f), f2u(-1.0f), f2u(4.0f)); check_lanes("sqrt_x_keeps_yzw", v_sqrt_x(v_make_vec4f(4.0f, 5.0f, 6.0f, 7.0f)), f2u(2.0f), f2u(5.0f), f2u(6.0f), f2u(7.0f)); @@ -124,7 +132,7 @@ int main() check_lanesi("cvtt", v_cvti_vec4i(a), 1u, 0xFFFFFFFEu, 3u, 0u); check_lanesi("cvtr", v_cvt_roundi_ieee(halves), 2u, 0xFFFFFFFEu, 4u, 0xFFFFFFFCu); -#if !defined(_TARGET_SIMD_NEON) +#if !defined(VECMATH_TEST_PACKED_X_CVT) check_lanesi("cvtt_ovf", v_cvti_vec4i(v_make_vec4f(no_fold(3e9f), no_fold(-3e9f), nanf_v, 100.75f)), 0x80000000u, 0x80000000u, 0x80000000u, 100u); check_lanesi("cvtr_ovf", v_cvt_roundi_ieee(v_make_vec4f(3e9f, -3e9f, nanf_v, 100.5f)), @@ -485,7 +493,7 @@ int main() check_int("vd_from_vec4f", vd_extract_z(d2) == 3.75 ? 1 : 0, 1); vec4d di = vd_cvt_from_vec4i(v_make_vec4i(3, -7, 123456, -2000000000)); check_int("vd_from_vec4i", vd_extract_w(di) == -2000000000.0 ? 1 : 0, 1); -#if !defined(_TARGET_SIMD_NEON) // NEON converts saturate; SSE/scalar yield INT32_MIN out of range +#if !defined(VECMATH_TEST_PACKED_X_CVT) // NEON and wasm converts saturate; SSE/scalar yield INT32_MIN out of range check_lanesi("vd_to_vec4i_oor", vd_cvt_to_vec4i(vd_make_vec4d(no_fold(3e9), no_fold(-3e9), 1.0, -1.0)), 0x80000000u, 0x80000000u, 1u, 0xFFFFFFFFu); #endif diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 0f1de7da76..7d3522b985 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -31,8 +31,16 @@ option(DAS_PUGIXML_DISABLED "Disable dasPUGIXML (xml parsing library)" ON) option(DAS_SQLITE_DISABLED "Disable dasSQLITE (sqlite3 library)" ON) add_compile_options(-msimd128) -add_compile_options(-msse2) add_compile_options(-mnontrapping-fptoint) +# vecmath's native wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on +# __wasm_simd128__; no -msse* compat layer is needed. Relaxed SIMD adds the fused +# f32x4.relaxed_madd behind v_madd/v_nmsub (real FMA on the hardware). One relaxed opcode makes +# the whole module refuse to instantiate on an engine without the proposal: Chrome 114+, +# Firefox 145+, Node 21+, wasmtime 15+ have it; Safari only behind a JavaScriptCore flag. +option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; needs an engine with the proposal)" ON) +if(DAS_WASM_RELAXED_SIMD) + add_compile_options(-mrelaxed-simd) +endif() # wasm64 (memory64): 8-byte pointers. With a 64-bit host, host-ptr-size == # target-ptr-size, so the JIT/AOT cross-compile bakes correct layouts (no From 5e876d8e036431990184e07a77783072e1ec18a3 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 22:59:40 -0700 Subject: [PATCH 2/5] wasm relaxed SIMD stays off by default: DAS_WASM_RELAXED_SIMD and DAS_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 --- modules/dasImgui/CMakeLists.txt | 5 +++-- web/CMakeLists.txt | 12 +++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/dasImgui/CMakeLists.txt b/modules/dasImgui/CMakeLists.txt index fbe1862518..945f6e8173 100644 --- a/modules/dasImgui/CMakeLists.txt +++ b/modules/dasImgui/CMakeLists.txt @@ -434,8 +434,9 @@ ELSEIF(EMSCRIPTEN) # same SIMD feature set as web/CMakeLists.txt: vecmath's wasm backend selects itself on # __wasm_simd128__, and the relaxed-SIMD knob mirrors DAS_WASM_RELAXED_SIMD so v_madd fuses - # (or not) on both sides of the archive boundary - OPTION(DAS_IMGUI_WASM_RELAXED_SIMD "Build the wasm archives with relaxed SIMD (matches DAS_WASM_RELAXED_SIMD)" ON) + # (or not) on both sides of the archive boundary. OFF by default: Safari refuses a module + # with any relaxed opcode (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md sec.12) + OPTION(DAS_IMGUI_WASM_RELAXED_SIMD "Build the wasm archives with relaxed SIMD (matches DAS_WASM_RELAXED_SIMD)" OFF) SET(IMGUI_WASM_FLAGS -fno-rtti -msimd128 -mnontrapping-fptoint diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 7d3522b985..e54312fc30 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -33,11 +33,13 @@ option(DAS_SQLITE_DISABLED "Disable dasSQLITE (sqlite3 library)" ON) add_compile_options(-msimd128) add_compile_options(-mnontrapping-fptoint) # vecmath's native wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on -# __wasm_simd128__; no -msse* compat layer is needed. Relaxed SIMD adds the fused -# f32x4.relaxed_madd behind v_madd/v_nmsub (real FMA on the hardware). One relaxed opcode makes -# the whole module refuse to instantiate on an engine without the proposal: Chrome 114+, -# Firefox 145+, Node 21+, wasmtime 15+ have it; Safari only behind a JavaScriptCore flag. -option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; needs an engine with the proposal)" ON) +# __wasm_simd128__; no -msse* compat layer is needed. Relaxed SIMD would add the fused +# f32x4.relaxed_madd behind v_madd/v_nmsub, but it stays OFF for the same reason the JIT's +# feature string carries no +relaxed-simd (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md +# sec.12): WebKit implements none of the relaxed opcodes, one of them anywhere in the module +# makes Safari refuse it at validation, and the browser builds ship to every engine. Opt in +# only for a target that never reaches Safari. +option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; Safari refuses the module)" OFF) if(DAS_WASM_RELAXED_SIMD) add_compile_options(-mrelaxed-simd) endif() From 9eb6980f308045414d879f28d9e9d20e92373820 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 23:12:20 -0700 Subject: [PATCH 3/5] the comment harvest over the wasm vecmath arc: the build-flag prose compresses 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 --- .github/workflows/wasm_build.yml | 2 +- include/vecmath/CLAUDE.md | 6 ++++++ include/vecmath/dag_vecMath_double.h | 2 +- include/vecmath/dag_vecMath_wasm.h | 2 -- modules/dasImgui/CMakeLists.txt | 5 +---- modules/dasImgui/REVIEW.md | 6 ++++++ skills/internal/preflight.md | 15 +++++++++------ tests-cpp/big/vecmath_backend/CMakeLists.txt | 14 +++++++------- web/CMakeLists.txt | 9 ++------- web/REVIEW.md | 13 +++++++++++++ 10 files changed, 46 insertions(+), 28 deletions(-) diff --git a/.github/workflows/wasm_build.yml b/.github/workflows/wasm_build.yml index 29cb60b1d8..3aad78d9ab 100644 --- a/.github/workflows/wasm_build.yml +++ b/.github/workflows/wasm_build.yml @@ -151,7 +151,7 @@ jobs: cd cmake_temp cmake -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake_preset }} -DDAS_FLEX_BISON_DISABLED=ON -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ ninja - # the vecmath backend battery, both arms as node-runnable pairs (tests-cpp/big/vecmath_backend) + # web/ adds the repo tree EXCLUDE_FROM_ALL - plain ninja builds neither target ninja test_vecmath_native test_vecmath_scalar - name: "Test: hello world via Node.js" diff --git a/include/vecmath/CLAUDE.md b/include/vecmath/CLAUDE.md index 0b279179ad..db0180abba 100644 --- a/include/vecmath/CLAUDE.md +++ b/include/vecmath/CLAUDE.md @@ -6,6 +6,12 @@ wasm SIMD128 and a scalar per-lane fallback for targets with no SIMD ISA behind unified C API. Used pervasively throughout the Dagor Engine for all performance-critical math: transforms, physics, BVH traversal, culling, animation, etc. +These headers are authored here and contributed upstream to Dagor Engine: a backend file follows +the comment shape of dag_vecMath_pc_sse.h / dag_vecMath_neon.h - a file header block stating the +backend contract, plus one-line mechanism comments at sites whose intrinsic choice or lane order +is not readable from the code - and the repo-wide "no new C++ comments" rule does not apply +inside this folder. + ## Key Types (dag_vecMathDecl.h) - `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a clang typed vector on wasm, a 16-byte struct on scalar) - `vec4i` -- 128-bit integer vector (__m128i / int32x4_t / an int32 typed vector on wasm) diff --git a/include/vecmath/dag_vecMath_double.h b/include/vecmath/dag_vecMath_double.h index 732ebd9952..3da088c137 100644 --- a/include/vecmath/dag_vecMath_double.h +++ b/include/vecmath/dag_vecMath_double.h @@ -387,7 +387,7 @@ VECTORCALL VECMATH_FINLINE vec4d vd_cvt_from_vec4i(vec4i a) return vd_from_halves(VECMATH_WASM_D(wasm_f64x2_convert_low_i32x4(VECMATH_WASM_V(a))), VECMATH_WASM_D(wasm_f64x2_convert_low_i32x4(VECMATH_WASM_V(v_permi_zwzw(a))))); } -// truncates toward zero, saturating like v_cvti_vec4i on this backend +// truncates toward zero, saturating like v_cvt_vec4i on this backend VECTORCALL VECMATH_FINLINE vec4i vd_cvt_to_vec4i(vec4d a) { vec4i lo = VECMATH_WASM_I(wasm_i32x4_trunc_sat_f64x2_zero(VECMATH_WASM_V(a.xy))); diff --git a/include/vecmath/dag_vecMath_wasm.h b/include/vecmath/dag_vecMath_wasm.h index 104d7ae235..bef9ea683a 100644 --- a/include/vecmath/dag_vecMath_wasm.h +++ b/include/vecmath/dag_vecMath_wasm.h @@ -365,7 +365,6 @@ VECTORCALL VECMATH_FINLINE vec4f v_div(vec4f a, vec4f b) { return VECMATH_WASM_F VECTORCALL VECMATH_FINLINE vec4f v_div_x(vec4f a, vec4f b) { return v_div(a, b); } VECTORCALL VECMATH_FINLINE vec4f v_sqrt(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_sqrt(VECMATH_WASM_V(a))); } VECTORCALL VECMATH_FINLINE vec4f v_sqrt_x(vec4f a) { return v_sqrt(a); } -// no estimate instructions in the ISA: every reciprocal form is the exact division VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise(vec4f a) { return v_div(V_C_ONE, a); } VECTORCALL VECMATH_FINLINE vec4f v_rcp_est(vec4f a) { return v_div(V_C_ONE, a); } VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise_x(vec4f a) { return v_div(V_C_ONE, a); } @@ -383,7 +382,6 @@ VECTORCALL VECMATH_FINLINE vec4f v_abs_diff(vec4f a, vec4f b) { return v_abs(v_s VECTORCALL VECMATH_FINLINE vec4f v_cmp_abs_ge(vec4f a, vec4f b) { return v_cmp_ge(v_abs(a), v_abs(b)); } VECTORCALL VECMATH_FINLINE vec4f v_cmp_abs_gt(vec4f a, vec4f b) { return v_cmp_gt(v_abs(a), v_abs(b)); } -// every two-source lane permutation is one i8x16.shuffle VECTORCALL VECMATH_FINLINE vec4f v_perm_xxyy(vec4f v) { return __builtin_shufflevector(v, v, 0, 0, 1, 1); } VECTORCALL VECMATH_FINLINE vec4f v_perm_xxzz(vec4f v) { return __builtin_shufflevector(v, v, 0, 0, 2, 2); } VECTORCALL VECMATH_FINLINE vec4f v_perm_xyxy(vec4f v) { return __builtin_shufflevector(v, v, 0, 1, 0, 1); } diff --git a/modules/dasImgui/CMakeLists.txt b/modules/dasImgui/CMakeLists.txt index 945f6e8173..1cd4ad110b 100644 --- a/modules/dasImgui/CMakeLists.txt +++ b/modules/dasImgui/CMakeLists.txt @@ -432,10 +432,7 @@ ELSEIF(EMSCRIPTEN) OPTION(DAS_IMGUI_WASM_PTHREADS "Build the wasm archives threaded (-pthread)" ON) MESSAGE(STATUS "dasImgui: emscripten/web build (3 module archives + FreeType; memory64=${DAS_IMGUI_WASM_MEMORY64} pthreads=${DAS_IMGUI_WASM_PTHREADS})") - # same SIMD feature set as web/CMakeLists.txt: vecmath's wasm backend selects itself on - # __wasm_simd128__, and the relaxed-SIMD knob mirrors DAS_WASM_RELAXED_SIMD so v_madd fuses - # (or not) on both sides of the archive boundary. OFF by default: Safari refuses a module - # with any relaxed opcode (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md sec.12) + # relaxed SIMD is OFF - Safari refuses a module with any relaxed opcode (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md#wasm-feature-string) OPTION(DAS_IMGUI_WASM_RELAXED_SIMD "Build the wasm archives with relaxed SIMD (matches DAS_WASM_RELAXED_SIMD)" OFF) SET(IMGUI_WASM_FLAGS -fno-rtti diff --git a/modules/dasImgui/REVIEW.md b/modules/dasImgui/REVIEW.md index b1a3ad2472..fe1090249d 100644 --- a/modules/dasImgui/REVIEW.md +++ b/modules/dasImgui/REVIEW.md @@ -12,3 +12,9 @@ answers to the `tests/` subfolder's checklist (`modules/dasImgui/tests/REVIEW.md under `modules/dasImgui/bind/` or `modules/dasImgui/src/`, runs the test suite on the author's host OS before the PR: `preflight --only imgui`** (the per-OS exclude split: module `CLAUDE.md` sec. Tests). + +**A diff that changes the wasm SIMD compile flags in this folder's `CMakeLists.txt` - +`-msimd128`, `-mnontrapping-fptoint`, `-mrelaxed-simd`, or the `DAS_IMGUI_WASM_RELAXED_SIMD` +default - makes the matching change to `web/CMakeLists.txt` (repo root) in the same change.** +The archives link into the binary that file builds, and a relaxed-SIMD mismatch fuses `v_madd` +on one side of the archive boundary and splits it on the other. diff --git a/skills/internal/preflight.md b/skills/internal/preflight.md index 88d9c745f6..589c38c248 100644 --- a/skills/internal/preflight.md +++ b/skills/internal/preflight.md @@ -65,7 +65,7 @@ working-tree copy. | `extended_checks.yml` (per-PR) | every PR | two darwin15-arm64 jobs, `core` and `modules` (`ci/ci_matrix.py extended`), ALL release modules ON - section below | | `extended_checks.yml` (nightly) | `schedule` cron (daily 04:00 UTC) + `workflow_dispatch` | one job each on linux, darwin15 and windows running every step (role `all`), including the ones too slow for a PR: tutorial dry-runs, the run form of examples, coverage, the nano cross-compile, the AST verify tree sweep, doc-verify | | `codeql.yml` | every PR and `master` push touching `src/`, `include/`, `modules/`, `tests-cpp/` + a weekly cron | CodeQL over the C++ surface, ~20 min on a PR; no local mirror, so it stays per PR | -| `wasm_build.yml` | every PR | emscripten build of `web/` on 3 OSes + `wasm_cross` | +| `wasm_build.yml` | every PR | emscripten build of `web/` on 3 OSes, the vecmath backend battery and `tests/language` under node, + `wasm_cross` | | `build_eastl.yml` | every PR | EASTL shadow-config build + no-fileio build (linux clang) | | `doc.yml` | only if `doc/**`, `daslib/**`, `src/builtin/**`, `modules/dasImgui/**`, `modules/dasVulkan/**`, or `modules/dasLLAMA/dasllama/**` changed | the doc gates | | `playground-e2e.yml` | only if `site/**` / `web/examples/ui/**` changed | Playwright on the web playground | @@ -218,11 +218,14 @@ build then went red. ## wasm_build.yml -`wasm_build`: emsdk build of `web/` + a Node hello-world. `wasm_cross`: -cross-compiles utility mains to wasm32 via dasLLVM and runs them under wasmtime, -emscripten **pinned to 5.0.3** (newer clang crashes on -`utils/gen1-to-gen2/ds_parser.cpp` diagnostics). Mirror = emsdk in WSL following -the workflow verbatim; for most changes let CI carry the lane. +`wasm_build`: emsdk build of `web/` (emsdk `latest`), then under the emsdk node the vecmath +backend battery - `ninja test_vecmath_native test_vecmath_scalar` in `web/cmake_temp`, then +`node --experimental-wasm-exnref output/tests/test_vecmath_.js` for both arms - and the +`tests/language` suite through `web/test/dastest_wasm.js`. `wasm_cross`: cross-compiles +utility mains to wasm32 via dasLLVM and runs them under wasmtime, emscripten **pinned to +5.0.3** (newer clang crashes on `utils/gen1-to-gen2/ds_parser.cpp` diagnostics). Mirror = the same emsdk +commands on the box (on Windows `EMSDK_PYTHON` must point at a python >= 3.10, the emsdk-bundled +one is older) or in WSL; for most changes let CI carry the lane. ## build_eastl.yml diff --git a/tests-cpp/big/vecmath_backend/CMakeLists.txt b/tests-cpp/big/vecmath_backend/CMakeLists.txt index 4d96fb3930..7778ec90a6 100644 --- a/tests-cpp/big/vecmath_backend/CMakeLists.txt +++ b/tests-cpp/big/vecmath_backend/CMakeLists.txt @@ -33,14 +33,14 @@ if(NOT DAS_VECMATH_SCALAR) add_dependencies(test-small test_vecmath_native) endif() -# Under emscripten (the web/ build) the native arm is the wasm SIMD128 backend and both arms -# become node-runnable .js + .wasm pairs under web/output/tests. The web scope's directory-wide -# link options embed daslib and disable main (-sINVOKE_RUN=0) for the interpreter binary; a -# backend test wants neither, so the pair keeps only the exception-model flags every object in -# that build carries. The wasm_build.yml lane builds and runs both under node. +# LINK_OPTIONS replaces the web scope's inherited flags: embedded daslib and -sINVOKE_RUN=0 belong to the interpreter binary, not a standalone test. if(EMSCRIPTEN) - target_compile_definitions(test_vecmath_native PRIVATE EXPECT_WASM=1) - foreach(_arm test_vecmath_scalar test_vecmath_native) + set(_wasm_arms test_vecmath_scalar) + if(NOT DAS_VECMATH_SCALAR) + target_compile_definitions(test_vecmath_native PRIVATE EXPECT_WASM=1) + list(APPEND _wasm_arms test_vecmath_native) + endif() + foreach(_arm ${_wasm_arms}) set_target_properties(${_arm} PROPERTIES LINK_OPTIONS "-fwasm-exceptions;-sWASM_LEGACY_EXCEPTIONS=0" RUNTIME_OUTPUT_DIRECTORY ${DAS_WEB_OUTPUT_DIR}/tests) diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index e54312fc30..ee67f7f1d2 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -32,13 +32,8 @@ option(DAS_SQLITE_DISABLED "Disable dasSQLITE (sqlite3 library)" ON) add_compile_options(-msimd128) add_compile_options(-mnontrapping-fptoint) -# vecmath's native wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on -# __wasm_simd128__; no -msse* compat layer is needed. Relaxed SIMD would add the fused -# f32x4.relaxed_madd behind v_madd/v_nmsub, but it stays OFF for the same reason the JIT's -# feature string carries no +relaxed-simd (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md -# sec.12): WebKit implements none of the relaxed opcodes, one of them anywhere in the module -# makes Safari refuse it at validation, and the browser builds ship to every engine. Opt in -# only for a target that never reaches Safari. +# vecmath's wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on __wasm_simd128__; no -msse* compat layer is needed. +# relaxed SIMD is OFF - Safari refuses a module with any relaxed opcode (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md#wasm-feature-string) option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; Safari refuses the module)" OFF) if(DAS_WASM_RELAXED_SIMD) add_compile_options(-mrelaxed-simd) diff --git a/web/REVIEW.md b/web/REVIEW.md index 8a5268e218..0b9c78c7b9 100644 --- a/web/REVIEW.md +++ b/web/REVIEW.md @@ -26,3 +26,16 @@ which staging step stopped copying that tree.** **A diff that adds a host to `REVIEW.das`'s `ALLOWED_HOSTS` states, in the PR body, what a visitor sends that host and whether the host sets cookies.** + +**A diff that changes `DAS_WASM_RELAXED_SIMD` to default on, or adds `-mrelaxed-simd` to a +build whose output is served, is a defect - leave it off and let the multiply-add split into a +multiply and an add.** Safari and every iOS browser refuse a whole module carrying one relaxed +opcode, so the page fails to load rather than running slower +(`modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md`, the wasm feature string section). + +**A diff that changes the wasm SIMD compile flags in this folder's `CMakeLists.txt` - +`-msimd128`, `-mnontrapping-fptoint`, `-mrelaxed-simd`, or the `DAS_WASM_RELAXED_SIMD` +default - makes the matching change to `modules/dasImgui/CMakeLists.txt` (repo root) in the +same change.** The dasImgui wasm archives link into the binary this folder builds, and a +relaxed-SIMD mismatch fuses `v_madd` on one side of the archive boundary and splits it on +the other. From db2bdc03bc275266983e37be329ea9ee8f3450de Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 23:45:24 -0700 Subject: [PATCH 4/5] the review round's fix batch over the wasm vecmath arc: dag_vecMath_wasm.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 --- .github/workflows/REVIEW.md | 17 +++--- .github/workflows/wasm_build.yml | 4 +- CMakeLists.txt | 1 + include/daScript/daScriptC.h | 5 +- include/vecmath/CLAUDE.md | 16 +++--- include/vecmath/dag_vecMath_double.h | 1 - include/vecmath/dag_vecMath_wasm.h | 48 +++++++---------- modules/dasAudio/src/convolution_reverb.h | 8 +-- modules/dasImgui/REVIEW.md | 9 ++-- modules/dasStbImage/CMakeLists.txt | 12 ----- skills/comment_style_hygiene.md | 6 ++- skills/internal/preflight.md | 26 ++++++--- tests-cpp/REVIEW.md | 4 +- tests-cpp/big/vecmath_backend/CMakeLists.txt | 12 ++++- .../vecmath_backend/test_vecmath_backend.cpp | 53 ++++++++++++++++--- web/CMakeLists.txt | 4 +- web/REVIEW.md | 28 +++++----- 17 files changed, 147 insertions(+), 107 deletions(-) diff --git a/.github/workflows/REVIEW.md b/.github/workflows/REVIEW.md index 3e55ae3e07..fdab9d9c3e 100644 --- a/.github/workflows/REVIEW.md +++ b/.github/workflows/REVIEW.md @@ -19,10 +19,12 @@ pull request.** `permissions` naming only the scopes its own steps use.** A job with no timeout holds its runners until GitHub's six-hour ceiling on one hung step. -**A workflow the diff adds, or whose trigger, matrix, or local mirror the diff changes, gets -its row in sec."What CI runs (per-PR + nightly)" of `skills/internal/preflight.md` (repo root) -added or corrected in the same change - its trigger, and its local mirror or the words that it -has none.** A lane the table does not list, or lists wrong, is one nobody mirrors before a push. +**A workflow the diff adds, or whose trigger, matrix, or local mirror the diff changes, updates +two places in `skills/internal/preflight.md` (repo root) in the same change: its row in +sec."What CI runs (per-PR + nightly)", which names its trigger and what the lane runs, and its +own section - the heading beginning `## .yml`; a lane with one section per matrix arm +(`build.yml`) corrects the arm the diff changes - which names its local mirror or says it has +none.** A lane missing or wrong in either place is one nobody mirrors before a push. **A per-PR check leaves the per-PR path only to the nightly cron (`github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'`), and the diff either names the @@ -31,9 +33,10 @@ preflight gate - a check `preflight` runs locally before a push - that keeps it cell has.** A per-PR job fits 35 minutes; what does not fit moves. **A diff that adds or changes a per-PR check, or adds, changes, or removes a step a per-PR -check depends on, states a run of that check's command, on the lane's platform, in its PR body -or commit message; a green run of that lane on the PR's head commit is that evidence.** A check -that fails for a non-defect turns a green branch red for everyone. +check depends on, states a run of that check's command on one of the lane's platforms, naming +which, in its PR body or commit message; a green run of that lane on the PR's head commit +covers the lane's other platforms.** A check that fails for a non-defect turns a green branch +red for everyone. **A step in `pages.yml` that names more than one id under `examples/games/` spells them as a `for g in ; do` loop, never inline.** `examples/games/REVIEW.das` (repo root) reads the diff --git a/.github/workflows/wasm_build.yml b/.github/workflows/wasm_build.yml index 3aad78d9ab..7b55477783 100644 --- a/.github/workflows/wasm_build.yml +++ b/.github/workflows/wasm_build.yml @@ -154,7 +154,7 @@ jobs: # web/ adds the repo tree EXCLUDE_FROM_ALL - plain ninja builds neither target ninja test_vecmath_native test_vecmath_scalar - - name: "Test: hello world via Node.js" + - name: "Test: language suite and vecmath battery via Node.js" run: | set -eux # Use emsdk-bundled Node (deterministic version that supports the @@ -164,9 +164,9 @@ jobs: cd web # Modern wasm EH proposal (try_table / exnref) is gated in Node 22. # Default-on in Node 24+. Force-enable for forward compat with current LTS. + "$EMSDK_NODE" --experimental-wasm-exnref test/dastest_wasm.js ../ ./output "$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_native.js "$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_scalar.js - "$EMSDK_NODE" --experimental-wasm-exnref test/dastest_wasm.js ../ ./output ########################################################### wasm_cross: diff --git a/CMakeLists.txt b/CMakeLists.txt index 73ecb040d9..629c888193 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -940,6 +940,7 @@ include/vecmath/dag_vecMath_neon.h include/vecmath/dag_vecMath_pc_sse.h include/vecmath/dag_vecMath_scalar.h include/vecmath/dag_vecMath_trig.h +include/vecmath/dag_vecMath_wasm.h ) list(SORT VECMATH_SRC) SOURCE_GROUP_FILES("vecmath" VECMATH_SRC) diff --git a/include/daScript/daScriptC.h b/include/daScript/daScriptC.h index 27942a154b..8506f61172 100644 --- a/include/daScript/daScriptC.h +++ b/include/daScript/daScriptC.h @@ -22,7 +22,7 @@ #define DAS_CC_API #endif //if target is not defined, try to auto-detect target (same order as vecmath/dag_vecMathDecl.h: -//wasm first, its -msse* compat layer predefines __SSE2__) +//wasm first, because emscripten's -msse* compat layer predefines __SSE2__) #if !defined(_TARGET_SIMD_SSE) && !defined(_TARGET_SIMD_NEON) && !defined(_TARGET_SIMD_SCALAR) && !defined(_TARGET_SIMD_WASM) #if defined(__wasm_simd128__) #define _TARGET_SIMD_WASM 1 @@ -56,7 +56,8 @@ typedef float32x4_t vec4f; typedef int32x4_t vec4i; #elif defined(_TARGET_SIMD_WASM) - // the same clang typed vectors vecmath/dag_vecMathDecl.h declares, spelled for C too + // typedefs, so declaring these in both headers is a legal redeclaration - the scalar + // branch declares structs, which is why it needs the guard #include typedef float vec4f __attribute__((__vector_size__(16), __aligned__(16))); typedef int32_t vec4i __attribute__((__vector_size__(16), __aligned__(16))); diff --git a/include/vecmath/CLAUDE.md b/include/vecmath/CLAUDE.md index db0180abba..9c97b78944 100644 --- a/include/vecmath/CLAUDE.md +++ b/include/vecmath/CLAUDE.md @@ -6,11 +6,10 @@ wasm SIMD128 and a scalar per-lane fallback for targets with no SIMD ISA behind unified C API. Used pervasively throughout the Dagor Engine for all performance-critical math: transforms, physics, BVH traversal, culling, animation, etc. -These headers are authored here and contributed upstream to Dagor Engine: a backend file follows -the comment shape of dag_vecMath_pc_sse.h / dag_vecMath_neon.h - a file header block stating the -backend contract, plus one-line mechanism comments at sites whose intrinsic choice or lane order -is not readable from the code - and the repo-wide "no new C++ comments" rule does not apply -inside this folder. +A backend file carries a header block stating the backend contract - which SSE/NEON semantics +it matches and where it deviates - plus one-line mechanism comments at sites whose intrinsic +choice or lane order is not readable from the code. These headers are authored here and +contributed upstream to Dagor Engine, so a backend reads like its siblings. ## Key Types (dag_vecMathDecl.h) - `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a clang typed vector on wasm, a 16-byte struct on scalar) @@ -89,9 +88,10 @@ v_triangle*). inputs into temporaries before the first store, v_mat44_transpose takes src by value, v_mat44_inverse43 copies its input first). Preserve this property when adding functions - callers write v_mat44_mul(m, m, rel) -- v_sel selectors must be canonical per-lane masks (all-ones/zero, as v_cmp_* produce): SSE4.1 - blendvps reads only the sign bit, but the SSE2 path and NEON vbsl select per bit - a sign-only - selector works on the PC build and silently breaks on other targets +- v_sel/v_seli read only the selector's sign bit on every backend; v_btsel/v_btseli select per + bit. Pass a canonical per-lane mask (all-ones/zero, as v_cmp_* produce) to either - a per-bit + pattern handed to v_sel picks the whole lane by bit 31 alone, and a sign-only pattern handed + to v_btsel takes bit 31 from one source and bits 0-30 from the other - v_norm* of a zero or near-zero vector produces inf/NaN lanes; v_norm*_safe(a, def) returns def when length^2 fails the unsafe-divisor check - Function results are usually fully defined: _x forms define .x only (see suffix scheme) and diff --git a/include/vecmath/dag_vecMath_double.h b/include/vecmath/dag_vecMath_double.h index 3da088c137..f62f15678d 100644 --- a/include/vecmath/dag_vecMath_double.h +++ b/include/vecmath/dag_vecMath_double.h @@ -339,7 +339,6 @@ VECTORCALL VECMATH_FINLINE vec4d vd_cross3(vec4d a, vec4d b) { VECTORCALL VECMATH_FINLINE vecmath_f64x2 vd_lo(vec4d a) { return a.xy; } VECTORCALL VECMATH_FINLINE vecmath_f64x2 vd_hi(vec4d a) { return a.zw; } VECTORCALL VECMATH_FINLINE vec4d vd_from_halves(vecmath_f64x2 lo, vecmath_f64x2 hi) { vec4d r; r.xy = lo; r.zw = hi; return r; } -#define VECMATH_WASM_D(a) ((vecmath_f64x2)(a)) VECTORCALL VECMATH_FINLINE vec4d vd_zero() { vecmath_f64x2 z = VECMATH_WASM_D(wasm_f64x2_const_splat(0.0)); return vd_from_halves(z, z); } VECTORCALL VECMATH_FINLINE vec4d vd_splats(double a) { vecmath_f64x2 s = VECMATH_WASM_D(wasm_f64x2_splat(a)); return vd_from_halves(s, s); } diff --git a/include/vecmath/dag_vecMath_wasm.h b/include/vecmath/dag_vecMath_wasm.h index bef9ea683a..7efcf529c4 100644 --- a/include/vecmath/dag_vecMath_wasm.h +++ b/include/vecmath/dag_vecMath_wasm.h @@ -13,9 +13,8 @@ // sign bit, shift counts past the lane width zero-fill (sign-fill for v_srai). Float->int // conversions saturate and map NaN to 0 (i32x4.trunc_sat), the NEON contract - SSE's INT_MIN // answer has no single-instruction form here. There are no estimate instructions: the _est and -// _unprecise reciprocal/rsqrt forms are the exact division. v_madd/v_nmsub are the fused -// f32x4.relaxed_madd/nmadd when the translation unit is built with -mrelaxed-simd (the -// __wasm_relaxed_simd__ predefine) and VECMATH_NO_FMA is not defined; otherwise mul+add. +// _unprecise reciprocal/rsqrt forms are the exact division. v_madd/v_nmsub fuse under +// -mrelaxed-simd: the product is not rounded before the add. #include #include @@ -23,6 +22,7 @@ #define VECMATH_WASM_V(a) ((v128_t)(a)) #define VECMATH_WASM_F(a) ((vec4f)(a)) #define VECMATH_WASM_I(a) ((vec4i)(a)) +#define VECMATH_WASM_D(a) ((vecmath_f64x2)(a)) #if defined(__wasm_relaxed_simd__) && !defined(VECMATH_NO_FMA) #define VECMATH_WASM_FMA 1 #else @@ -176,18 +176,12 @@ VECTORCALL VECMATH_FINLINE vec4i v_subi(vec4i a, vec4i b) { return VECMATH_WASM_ VECTORCALL VECMATH_FINLINE vec4i v_muli(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_mul(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } // pair ops fold (x,y),(z,w) of a into .xy and of b into .zw, the SSE haddps lane order -VECTORCALL VECMATH_FINLINE vec4f v_min_pairs(vec4f a, vec4f b) -{ return v_min(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } -VECTORCALL VECMATH_FINLINE vec4f v_max_pairs(vec4f a, vec4f b) -{ return v_max(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } -VECTORCALL VECMATH_FINLINE vec4f v_add_pairs(vec4f a, vec4f b) -{ return v_add(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } -VECTORCALL VECMATH_FINLINE vec4i v_addi_pairs(vec4i a, vec4i b) -{ return v_addi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } -VECTORCALL VECMATH_FINLINE vec4i v_mini_pairs(vec4i a, vec4i b) -{ return v_mini(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } -VECTORCALL VECMATH_FINLINE vec4i v_maxi_pairs(vec4i a, vec4i b) -{ return v_maxi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4f v_min_pairs(vec4f a, vec4f b) { return v_min(v_perm_xzac(a, b), v_perm_ywbd(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_max_pairs(vec4f a, vec4f b) { return v_max(v_perm_xzac(a, b), v_perm_ywbd(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_add_pairs(vec4f a, vec4f b) { return v_add(v_perm_xzac(a, b), v_perm_ywbd(a, b)); } +VECTORCALL VECMATH_FINLINE vec4i v_addi_pairs(vec4i a, vec4i b) { return v_addi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4i v_mini_pairs(vec4i a, vec4i b) { return v_mini(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } +VECTORCALL VECMATH_FINLINE vec4i v_maxi_pairs(vec4i a, vec4i b) { return v_maxi(__builtin_shufflevector(a, b, 0, 2, 4, 6), __builtin_shufflevector(a, b, 1, 3, 5, 7)); } VECTORCALL VECMATH_FINLINE bool v_test_all_bits_zeros(vec4f a) { return !wasm_v128_any_true(VECMATH_WASM_V(a)); } VECTORCALL VECMATH_FINLINE bool v_test_all_bits_ones(vec4f a) { return !wasm_v128_any_true(wasm_v128_not(VECMATH_WASM_V(a))); } @@ -209,11 +203,7 @@ VECTORCALL VECMATH_FINLINE vec4f v_cmp_gt(vec4f a, vec4f b) { return VECMATH_WAS VECTORCALL VECMATH_FINLINE vec4i v_cmp_lti(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_lt(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } VECTORCALL VECMATH_FINLINE vec4i v_cmp_gti(vec4i a, vec4i b) { return VECMATH_WASM_I(wasm_i32x4_gt(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } -VECTORCALL VECMATH_FINLINE vec4f is_neg_special(vec4f a) -{ - vec4f msbit = v_msbit(); - return v_cmp_eqi(VECMATH_WASM_F(wasm_v128_and(VECMATH_WASM_V(a), VECMATH_WASM_V(msbit))), msbit); -} +VECTORCALL VECMATH_FINLINE vec4f is_neg_special(vec4f a) { return v_cast_vec4f(v_srai(v_cast_vec4i(a), 31)); } VECTORCALL VECMATH_FINLINE vec4f v_and(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_and(VECMATH_WASM_V(a), VECMATH_WASM_V(b))); } VECTORCALL VECMATH_FINLINE vec4f v_andnot(vec4f a, vec4f b) { return VECMATH_WASM_F(wasm_v128_andnot(VECMATH_WASM_V(b), VECMATH_WASM_V(a))); } @@ -366,13 +356,13 @@ VECTORCALL VECMATH_FINLINE vec4f v_div_x(vec4f a, vec4f b) { return v_div(a, b); VECTORCALL VECMATH_FINLINE vec4f v_sqrt(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_sqrt(VECMATH_WASM_V(a))); } VECTORCALL VECMATH_FINLINE vec4f v_sqrt_x(vec4f a) { return v_sqrt(a); } VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise(vec4f a) { return v_div(V_C_ONE, a); } -VECTORCALL VECMATH_FINLINE vec4f v_rcp_est(vec4f a) { return v_div(V_C_ONE, a); } -VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise_x(vec4f a) { return v_div(V_C_ONE, a); } -VECTORCALL VECMATH_FINLINE vec4f v_rcp_est_x(vec4f a) { return v_div(V_C_ONE, a); } -VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } -VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise_x(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } -VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } -VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est_x(vec4f a) { return v_div(V_C_ONE, v_sqrt(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_est(vec4f a) { return v_rcp_unprecise(a); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_unprecise_x(vec4f a) { return v_rcp_unprecise(a); } +VECTORCALL VECMATH_FINLINE vec4f v_rcp_est_x(vec4f a) { return v_rcp_unprecise(a); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise(vec4f a) { return v_rcp_unprecise(v_sqrt(a)); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_unprecise_x(vec4f a) { return v_rsqrt_unprecise(a); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est(vec4f a) { return v_rsqrt_unprecise(a); } +VECTORCALL VECMATH_FINLINE vec4f v_rsqrt_est_x(vec4f a) { return v_rsqrt_unprecise(a); } VECTORCALL VECMATH_FINLINE vec4f v_neg(vec4f a) { return VECMATH_WASM_F(wasm_f32x4_neg(VECMATH_WASM_V(a))); } VECTORCALL VECMATH_FINLINE vec4i v_negi(vec4i a) { return VECMATH_WASM_I(wasm_i32x4_neg(VECMATH_WASM_V(a))); } @@ -458,7 +448,7 @@ VECTORCALL VECMATH_FINLINE vec4f v_dot2(vec4f a, vec4f b) { return v_splat_x(v_d VECTORCALL VECMATH_FINLINE vec4f v_dot3_x(vec4f a, vec4f b) { return v_hadd3_x(v_mul(a, b)); } VECTORCALL VECMATH_FINLINE vec4f v_dot3(vec4f a, vec4f b) { return v_splat_x(v_dot3_x(a, b)); } VECTORCALL VECMATH_FINLINE vec4f v_dot4_x(vec4f a, vec4f b) { return v_hadd4_x(v_mul(a, b)); } -VECTORCALL VECMATH_FINLINE vec4f v_dot4(vec4f a, vec4f b) { return v_hadd4_x(v_mul(a, b)); } +VECTORCALL VECMATH_FINLINE vec4f v_dot4(vec4f a, vec4f b) { return v_dot4_x(a, b); } VECTORCALL VECMATH_FINLINE vec3f v_cross3(vec3f a, vec3f b) { @@ -608,7 +598,7 @@ VECTORCALL VECMATH_FINLINE vec4i v_slli(vec4i v, int bits) VECTORCALL VECMATH_FINLINE vec4i v_srli(vec4i v, int bits) { return (unsigned)bits < 32u ? VECMATH_WASM_I(wasm_u32x4_shr(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } VECTORCALL VECMATH_FINLINE vec4i v_srai(vec4i v, int bits) -{ return VECMATH_WASM_I(wasm_i32x4_shr(VECMATH_WASM_V(v), (unsigned)bits < 31u ? (uint32_t)bits : 31u)); } +{ return VECMATH_WASM_I(wasm_i32x4_shr(VECMATH_WASM_V(v), (unsigned)bits < 32u ? (uint32_t)bits : 31u)); } VECTORCALL VECMATH_FINLINE vec4i v_slli_64(vec4i v, int bits) { return (unsigned)bits < 64u ? VECMATH_WASM_I(wasm_i64x2_shl(VECMATH_WASM_V(v), (uint32_t)bits)) : v_zeroi(); } VECTORCALL VECMATH_FINLINE vec4i v_srli_64(vec4i v, int bits) diff --git a/modules/dasAudio/src/convolution_reverb.h b/modules/dasAudio/src/convolution_reverb.h index c6fbe9314e..029e14b214 100644 --- a/modules/dasAudio/src/convolution_reverb.h +++ b/modules/dasAudio/src/convolution_reverb.h @@ -237,12 +237,12 @@ static void conv_reverb_generate_ir(ConvolutionReverb * rev, float * ir_left, fl // acc[i] += a[i] * b[i] over `count` complex numbers in interleaved [re,im] layout — the // per-partition convolution kernel (P calls per block per channel; the reverb's hot loop). -// Vectorized via dag_vecMath (one path for SSE + NEON): four complex per iteration, deinterleaved +// Vectorized via dag_vecMath (one path for SSE + NEON + wasm): four complex per iteration, deinterleaved // into real/imag lanes so a plain msub/madd computes ar*br-ai*bi and ar*bi+ai*br with no addsub. -// Every op is SSE2/NEON baseline (v_madd degrades to mul+add without FMA), so there is no runtime -// CPU dispatch; the scalar fallback only covers targets with no dag_vecMath SIMD backend. +// Every op is SSE2/NEON/SIMD128 baseline (v_madd degrades to mul+add without FMA), so there is no +// runtime CPU dispatch; the scalar fallback only covers targets with no dag_vecMath SIMD backend. static void conv_reverb_complex_multiply_acc(float * acc, const float * a, const float * b, uint32_t count) { -#if defined(_TARGET_SIMD_SSE) || defined(_TARGET_SIMD_NEON) +#if defined(_TARGET_SIMD_SSE) || defined(_TARGET_SIMD_NEON) || defined(_TARGET_SIMD_WASM) uint32_t i = 0; uint32_t vN = count & ~3u; // largest multiple of 4 for (; i < vN; i += 4) { diff --git a/modules/dasImgui/REVIEW.md b/modules/dasImgui/REVIEW.md index fe1090249d..996713a28a 100644 --- a/modules/dasImgui/REVIEW.md +++ b/modules/dasImgui/REVIEW.md @@ -13,8 +13,7 @@ under `modules/dasImgui/bind/` or `modules/dasImgui/src/`, runs the test suite o host OS before the PR: `preflight --only imgui`** (the per-OS exclude split: module `CLAUDE.md` sec. Tests). -**A diff that changes the wasm SIMD compile flags in this folder's `CMakeLists.txt` - -`-msimd128`, `-mnontrapping-fptoint`, `-mrelaxed-simd`, or the `DAS_IMGUI_WASM_RELAXED_SIMD` -default - makes the matching change to `web/CMakeLists.txt` (repo root) in the same change.** -The archives link into the binary that file builds, and a relaxed-SIMD mismatch fuses `v_madd` -on one side of the archive boundary and splits it on the other. +**A diff that changes a `-m` feature flag in this folder's `CMakeLists.txt` `IMGUI_WASM_FLAGS`, +or the `DAS_IMGUI_WASM_RELAXED_SIMD` default, makes the matching change to `web/CMakeLists.txt`'s +`add_compile_options` and `DAS_WASM_RELAXED_SIMD` in the same change.** Neither build inherits +the other's flags. diff --git a/modules/dasStbImage/CMakeLists.txt b/modules/dasStbImage/CMakeLists.txt index 226601d217..dab716fecb 100644 --- a/modules/dasStbImage/CMakeLists.txt +++ b/modules/dasStbImage/CMakeLists.txt @@ -52,18 +52,6 @@ IF ((NOT DAS_STBIMAGE_INCLUDED) AND (NOT ${DAS_STBIMAGE_DISABLED})) ENDIF() ADD_MODULE_PUB_LIB(libDasModuleStbImage dasModuleStbImage ${DAS_STBIMAGE_MODULE_SRC} ${DAS_STBIMAGE_MODULE_PLATFORM_SRC}) - # On emscripten the wasm build passes BOTH -msse2 (defines __SSE2__) and -msimd128 - # (defines __wasm_simd128__), so the vendored stb headers light up their SSE2 AND - # WASM SIMD paths at once -> stb_image_resize2.h initializes a 4x-int32 vector with - # 64-bit-packed SSE constants -> -Wc++11-narrowing errors. -msse2 is load-bearing - # for the interpreter's vecmath, so force stb to its scalar paths on wasm instead - # (decode/resize slightly slower, fully correct). - IF(EMSCRIPTEN) - TARGET_COMPILE_DEFINITIONS(libDasModuleStbImage PRIVATE STBI_NO_SIMD STBIR_NO_SIMD) - IF(TARGET dasModuleStbImage) - TARGET_COMPILE_DEFINITIONS(dasModuleStbImage PRIVATE STBI_NO_SIMD STBIR_NO_SIMD) - ENDIF() - ENDIF() TARGET_LINK_LIBRARIES(libDasModuleStbImage PUBLIC ${STBIMAGE_LIBRARIES}) # GNU-only flag; clang-cl rejects it IF(NOT MSVC) diff --git a/skills/comment_style_hygiene.md b/skills/comment_style_hygiene.md index df3e3870c2..95f291aa32 100644 --- a/skills/comment_style_hygiene.md +++ b/skills/comment_style_hygiene.md @@ -33,7 +33,11 @@ added are not. rules is fork drift. A folder is vendored when it is a `3rdparty/` tree or its own `CLAUDE.md` names an upstream and records the synced revision; inside one, the files that `CLAUDE.md` marks as written or maintained here - fork-local hooks, this repo's - own docs - answer to these rules, and every file it leaves to upstream does not. + own docs - answer to these rules, and every file it leaves to upstream does not. The + outbound direction is the same rule: a folder whose `CLAUDE.md` names the upstream project + its files are contributed to, and states the comment shape they keep there, answers to that + shape for those files (`include/vecmath/`); the deletion test still settles anything the + stated shape does not cover. Rules marked *(lintable)* are mechanical enough for a lint to enforce; where a rule notes an existing lint, the lint compels - an unnoted rule is the reviewer's. diff --git a/skills/internal/preflight.md b/skills/internal/preflight.md index 589c38c248..9c3a0ce946 100644 --- a/skills/internal/preflight.md +++ b/skills/internal/preflight.md @@ -218,14 +218,24 @@ build then went red. ## wasm_build.yml -`wasm_build`: emsdk build of `web/` (emsdk `latest`), then under the emsdk node the vecmath -backend battery - `ninja test_vecmath_native test_vecmath_scalar` in `web/cmake_temp`, then -`node --experimental-wasm-exnref output/tests/test_vecmath_.js` for both arms - and the -`tests/language` suite through `web/test/dastest_wasm.js`. `wasm_cross`: cross-compiles -utility mains to wasm32 via dasLLVM and runs them under wasmtime, emscripten **pinned to -5.0.3** (newer clang crashes on `utils/gen1-to-gen2/ds_parser.cpp` diagnostics). Mirror = the same emsdk -commands on the box (on Windows `EMSDK_PYTHON` must point at a python >= 3.10, the emsdk-bundled -one is older) or in WSL; for most changes let CI carry the lane. +`wasm_build`: emsdk build of `web/` (emsdk `latest`), then under the emsdk node +(`"$EMSDK_NODE"`, the system node may be older) two things - the `tests/language` suite +through `web/test/dastest_wasm.js`, and the vecmath backend battery, which runs the same C++ +rows twice: `test_vecmath_native` on the wasm SIMD128 backend and `test_vecmath_scalar` on the +per-lane fallback it is checked against. Build both in `web/cmake_temp` +(`ninja test_vecmath_native test_vecmath_scalar` - `web/` adds the repo tree +`EXCLUDE_FROM_ALL`, so plain `ninja` builds neither), then from `web/`: + +``` +"$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_native.js +"$EMSDK_NODE" --experimental-wasm-exnref output/tests/test_vecmath_scalar.js +``` + +`wasm_cross`: cross-compiles utility mains to wasm32 via dasLLVM and runs them under +wasmtime, emscripten **pinned to 5.0.3** (newer clang crashes on +`utils/gen1-to-gen2/ds_parser.cpp` diagnostics). Mirror, either lane = that job's own emsdk +commands verbatim, its version included (on Windows `EMSDK_PYTHON` must point at a python +>= 3.10, the emsdk-bundled one is older); for most changes let CI carry the lane. ## build_eastl.yml diff --git a/tests-cpp/REVIEW.md b/tests-cpp/REVIEW.md index 5fd8b330f5..290511b396 100644 --- a/tests-cpp/REVIEW.md +++ b/tests-cpp/REVIEW.md @@ -6,8 +6,8 @@ doc: `skills/internal/writing_cpp_tests.md`. **A `*_pin.cpp` file, wherever the diff puts it, answers to the `small/` subfolder's checklist as well as this one.** -**A test that owns its own `CMakeLists.txt`, wherever the diff puts it, answers to the -`big/` subfolder's checklist as well as this one.** +**A test whose ctest labels include `big`, wherever the diff puts it, answers to the `big/` +subfolder's checklist as well as this one.** **A C++ test a diff adds or changes that some lane running the suite cannot fail on - the test skips there, or its subject sits behind a `#if` that lane leaves undefined - names in the PR diff --git a/tests-cpp/big/vecmath_backend/CMakeLists.txt b/tests-cpp/big/vecmath_backend/CMakeLists.txt index 7778ec90a6..a2b3e02b7c 100644 --- a/tests-cpp/big/vecmath_backend/CMakeLists.txt +++ b/tests-cpp/big/vecmath_backend/CMakeLists.txt @@ -33,8 +33,16 @@ if(NOT DAS_VECMATH_SCALAR) add_dependencies(test-small test_vecmath_native) endif() -# LINK_OPTIONS replaces the web scope's inherited flags: embedded daslib and -sINVOKE_RUN=0 belong to the interpreter binary, not a standalone test. +# LINK_OPTIONS replaces the web scope's inherited flags: embedded daslib and -sINVOKE_RUN=0 belong to the +# interpreter binary, not a standalone test; the ABI flags the objects were compiled with stay. if(EMSCRIPTEN) + set(_wasm_link_options -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0) + if(DAS_WASM_MEMORY64) + list(APPEND _wasm_link_options -sMEMORY64=2) + endif() + if(DAS_WASM_PTHREADS) + list(APPEND _wasm_link_options -pthread) + endif() set(_wasm_arms test_vecmath_scalar) if(NOT DAS_VECMATH_SCALAR) target_compile_definitions(test_vecmath_native PRIVATE EXPECT_WASM=1) @@ -42,7 +50,7 @@ if(EMSCRIPTEN) endif() foreach(_arm ${_wasm_arms}) set_target_properties(${_arm} PROPERTIES - LINK_OPTIONS "-fwasm-exceptions;-sWASM_LEGACY_EXCEPTIONS=0" + LINK_OPTIONS "${_wasm_link_options}" RUNTIME_OUTPUT_DIRECTORY ${DAS_WEB_OUTPUT_DIR}/tests) endforeach() endif() diff --git a/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp b/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp index b4201ff214..24d9f49d44 100644 --- a/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp +++ b/tests-cpp/big/vecmath_backend/test_vecmath_backend.cpp @@ -4,8 +4,8 @@ // with !_TARGET_SIMD_NEON pin SSE-flavored semantics the scalar backend promises // to match (NaN/tie ordering, sign-bit select, out-of-range converts, shift // counts past the lane width) - NEON diverges there by its own contract. The wasm -// backend keeps every SSE row but two it shares with NEON (VECMATH_TEST_PACKED_X_CVT -// below): _x forms are the packed op, and float->int converts saturate. +// backend keeps every SSE row but the two contracts it shares with NEON: _x forms are +// the packed op, and float->int converts saturate. #if defined(__FAST_MATH__) || defined(_M_FP_FAST) #error "the rows pin IEEE answers a fast-math build may fold; both arms are pinned to precise math in CMakeLists.txt" @@ -36,7 +36,8 @@ #error this target must select the wasm SIMD128 vecmath backend #endif #if defined(_TARGET_SIMD_NEON) || defined(_TARGET_SIMD_WASM) -#define VECMATH_TEST_PACKED_X_CVT 1 +#define VECMATH_TEST_X_IS_PACKED 1 +#define VECMATH_TEST_CVT_SATURATES 1 #endif static int g_failed = 0; @@ -111,7 +112,7 @@ int main() check_int("add_x", (long long)f2u(v_extract_x(v_add_x(a, b))), (long long)f2u(3.5f)); check_int("nmsub_x", (long long)f2u(v_extract_x(v_nmsub_x(a, b, b))), (long long)f2u(-1.0f)); check_int("sqrt_x", (long long)f2u(v_extract_x(v_sqrt_x(v_make_vec4f(4.0f, 5.0f, 6.0f, 7.0f)))), (long long)f2u(2.0f)); -#if !defined(VECMATH_TEST_PACKED_X_CVT) +#if !defined(VECMATH_TEST_X_IS_PACKED) check_lanes("add_x_keeps_yzw", v_add_x(a, b), f2u(3.5f), f2u(-2.25f), f2u(3.75f), f2u(-0.5f)); check_lanes("nmsub_x_keeps_c_yzw", v_nmsub_x(a, b, b), f2u(-1.0f), f2u(0.5f), f2u(-1.0f), f2u(4.0f)); check_lanes("sqrt_x_keeps_yzw", v_sqrt_x(v_make_vec4f(4.0f, 5.0f, 6.0f, 7.0f)), f2u(2.0f), f2u(5.0f), f2u(6.0f), f2u(7.0f)); @@ -132,11 +133,17 @@ int main() check_lanesi("cvtt", v_cvti_vec4i(a), 1u, 0xFFFFFFFEu, 3u, 0u); check_lanesi("cvtr", v_cvt_roundi_ieee(halves), 2u, 0xFFFFFFFEu, 4u, 0xFFFFFFFCu); -#if !defined(VECMATH_TEST_PACKED_X_CVT) +#if !defined(VECMATH_TEST_CVT_SATURATES) check_lanesi("cvtt_ovf", v_cvti_vec4i(v_make_vec4f(no_fold(3e9f), no_fold(-3e9f), nanf_v, 100.75f)), 0x80000000u, 0x80000000u, 0x80000000u, 100u); check_lanesi("cvtr_ovf", v_cvt_roundi_ieee(v_make_vec4f(3e9f, -3e9f, nanf_v, 100.5f)), 0x80000000u, 0x80000000u, 0x80000000u, 100u); +#endif +#if defined(VECMATH_TEST_CVT_SATURATES) + check_lanesi("cvtt_sat", v_cvti_vec4i(v_make_vec4f(no_fold(3e9f), no_fold(-3e9f), nanf_v, 100.75f)), + 0x7FFFFFFFu, 0x80000000u, 0u, 100u); + check_lanesi("cvtr_sat", v_cvt_roundi_ieee(v_make_vec4f(3e9f, -3e9f, nanf_v, 100.5f)), + 0x7FFFFFFFu, 0x80000000u, 0u, 100u); #endif check_lanesi("cvt_floori", v_cvt_floori(halves), 2u, 0xFFFFFFFDu, 3u, 0xFFFFFFFCu); check_lanes("cvti2f", v_cvti_vec4f(ia), f2u(3.0f), f2u(-7.0f), f2u(123456.0f), f2u(-2000000000.0f)); @@ -493,7 +500,7 @@ int main() check_int("vd_from_vec4f", vd_extract_z(d2) == 3.75 ? 1 : 0, 1); vec4d di = vd_cvt_from_vec4i(v_make_vec4i(3, -7, 123456, -2000000000)); check_int("vd_from_vec4i", vd_extract_w(di) == -2000000000.0 ? 1 : 0, 1); -#if !defined(VECMATH_TEST_PACKED_X_CVT) // NEON and wasm converts saturate; SSE/scalar yield INT32_MIN out of range +#if !defined(VECMATH_TEST_CVT_SATURATES) // NEON and wasm converts saturate; SSE/scalar yield INT32_MIN out of range check_lanesi("vd_to_vec4i_oor", vd_cvt_to_vec4i(vd_make_vec4d(no_fold(3e9), no_fold(-3e9), 1.0, -1.0)), 0x80000000u, 0x80000000u, 1u, 0xFFFFFFFFu); #endif @@ -510,6 +517,40 @@ int main() check_double("vd_stu_p3_sentinel", outd[3], -1.0); } + // pshufb key semantics: bits 4..6 of a key byte are ignored, bit 7 zeroes the lane + check_lanesi("perm_i8_mask", v_perm_i8(v_make_vec4i(0x44332211, int(0x88776655), int(0xCCBBAA99), 0x00FFEEDD), + v_make_vec4i(0x70605040, int(0x8F8E8D8C), 0x1F1E1D1C, 0x03020100)), + 0x11111111u, 0u, 0x00FFEEDDu, 0x44332211u); + { + const vec4i q = v_make_vec4i(0x10, 0, -16, -1); // as u64 lanes: 0x10, 0xFFFFFFFFFFFFFFF0 + check_lanesi("slli_64", v_slli_64(q, 4), 0x100u, 0u, 0xFFFFFF00u, 0xFFFFFFFFu); + check_lanesi("srli_64", v_srli_64(q, 4), 1u, 0u, 0xFFFFFFFFu, 0x0FFFFFFFu); + } + check_lanesi("mulhi16", v_mulhi16(v_make_vec4i(0x40004000, int(0xC000C000), 0x40004000, int(0xC000C000)), v_splatsi16(0x4000)), + 0x10001000u, 0xF000F000u, 0x10001000u, 0xF000F000u); + check_lanesi("cvt_byte", v_cvt_byte_vec4i(0x04030201u), 1u, 2u, 3u, 4u); + check_lanes("perm_zayx", v_perm_zayx(a, b), f2u(3.75f), f2u(2.0f), f2u(-2.25f), f2u(1.5f)); + check_lanesi("interleave_lo8", v_interleave_lo_i8(v_make_vec4i(0x03020100, 0x07060504, 0, 0), v_make_vec4i(0x13121110, 0x17161514, 0, 0)), + 0x11011000u, 0x13031202u, 0x15051404u, 0x17071606u); + check_int("check_xz_all_true", v_check_xz_all_true(v_cast_vec4f(v_make_vec4i(-1, 0, -1, 0))) ? 1 : 0, 1); + check_int("check_xz_all_true_false_dir", v_check_xz_all_true(v_cast_vec4f(v_make_vec4i(-1, -1, 0, -1))) ? 1 : 0, 0); + { + mat44f m; + m.col0 = v_make_vec4f(2, 0, 0, 0); + m.col1 = v_make_vec4f(0, 3, 0, 0); + m.col2 = v_make_vec4f(1, 0, 4, 0); + m.col3 = v_make_vec4f(5, 6, 7, 1); + float m43[12] = {0}; + v_mat_43cu_from_mat44(m43, m); + const float want[12] = {2, 0, 0, 0, 3, 0, 1, 0, 4, 5, 6, 7}; + for (int k = 0; k < 12; k++) + if (m43[k] != want[k]) { printf("FAIL mat_43cu_from_mat44 [%d]: got %g want %g\n", k, m43[k], want[k]); g_failed++; break; } + } +#if defined(_TARGET_SIMD_WASM) // NEON narrows a 64-bit convert instead; the wasm arm saturates into 32 bits + check_lanesi("vd_to_vec4i_sat", vd_cvt_to_vec4i(vd_make_vec4d(no_fold(3e9), no_fold(-3e9), 1.0, -1.0)), + 0x7FFFFFFFu, 0x80000000u, 1u, 0xFFFFFFFFu); +#endif + if (g_failed) { printf("%d vecmath backend checks FAILED\n", g_failed); return 1; } printf("all vecmath backend checks passed\n"); return 0; diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index ee67f7f1d2..c4f53407f4 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -33,8 +33,8 @@ option(DAS_SQLITE_DISABLED "Disable dasSQLITE (sqlite3 library)" ON) add_compile_options(-msimd128) add_compile_options(-mnontrapping-fptoint) # vecmath's wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on __wasm_simd128__; no -msse* compat layer is needed. -# relaxed SIMD is OFF - Safari refuses a module with any relaxed opcode (modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md#wasm-feature-string) -option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; Safari refuses the module)" OFF) +# modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md#wasm-feature-string +option(DAS_WASM_RELAXED_SIMD "Build wasm output with relaxed SIMD (fused v_madd; Safari refuses a module with any relaxed opcode)" OFF) if(DAS_WASM_RELAXED_SIMD) add_compile_options(-mrelaxed-simd) endif() diff --git a/web/REVIEW.md b/web/REVIEW.md index 0b9c78c7b9..d1bac95626 100644 --- a/web/REVIEW.md +++ b/web/REVIEW.md @@ -1,10 +1,9 @@ # web (the WASM build and its shells) Code Review Checklist **Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture -doc: `README.md`. A file -under this folder is served when the deploy (`.github/workflows/pages.yml`, repo root), -`daspkg release wasm`, or a build step that feeds either copies it into a page a visitor -loads. +doc: `README.md`. A file under this folder is served when the deploy +(`.github/workflows/pages.yml`, repo root), `daspkg release wasm`, or a build step that feeds +either copies it into a page a visitor loads. **A playground UI file - an `examples/ui/src` script or stylesheet, or an `examples/ui/samples` bundle - answers to `examples/ui/REVIEW.md`, wherever the diff puts @@ -27,15 +26,12 @@ which staging step stopped copying that tree.** **A diff that adds a host to `REVIEW.das`'s `ALLOWED_HOSTS` states, in the PR body, what a visitor sends that host and whether the host sets cookies.** -**A diff that changes `DAS_WASM_RELAXED_SIMD` to default on, or adds `-mrelaxed-simd` to a -build whose output is served, is a defect - leave it off and let the multiply-add split into a -multiply and an add.** Safari and every iOS browser refuse a whole module carrying one relaxed -opcode, so the page fails to load rather than running slower -(`modules/dasLLVM/ARCHITECTURE_TARGET_FEATURES.md`, the wasm feature string section). - -**A diff that changes the wasm SIMD compile flags in this folder's `CMakeLists.txt` - -`-msimd128`, `-mnontrapping-fptoint`, `-mrelaxed-simd`, or the `DAS_WASM_RELAXED_SIMD` -default - makes the matching change to `modules/dasImgui/CMakeLists.txt` (repo root) in the -same change.** The dasImgui wasm archives link into the binary this folder builds, and a -relaxed-SIMD mismatch fuses `v_madd` on one side of the archive boundary and splits it on -the other. +**A diff that flips the `DAS_WASM_RELAXED_SIMD` default to ON, or makes `-mrelaxed-simd` +unconditional in this folder's `CMakeLists.txt`, is a defect.** Safari and every iOS browser +refuse a whole module carrying one relaxed opcode, so the page fails to load rather than +running slower. + +**A diff that changes a `-m` feature flag in this folder's `CMakeLists.txt` +`add_compile_options`, or the `DAS_WASM_RELAXED_SIMD` default, makes the matching change to +`modules/dasImgui/CMakeLists.txt`'s `IMGUI_WASM_FLAGS` and `DAS_IMGUI_WASM_RELAXED_SIMD` in the +same change.** Neither build inherits the other's flags. From b2f3ca3f82dbff8fb08139071f298569f10845f6 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 16 Sep 2026 23:51:00 -0700 Subject: [PATCH 5/5] the cold re-read's closing batch over the wasm vecmath arc's rule documents: 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 --- .github/workflows/REVIEW.md | 14 ++++++++------ include/vecmath/CLAUDE.md | 5 +++-- modules/dasImgui/REVIEW.md | 11 +++++++---- skills/comment_style_hygiene.md | 10 +++++----- web/REVIEW.md | 5 ++--- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/.github/workflows/REVIEW.md b/.github/workflows/REVIEW.md index fdab9d9c3e..a0c677a10d 100644 --- a/.github/workflows/REVIEW.md +++ b/.github/workflows/REVIEW.md @@ -19,12 +19,14 @@ pull request.** `permissions` naming only the scopes its own steps use.** A job with no timeout holds its runners until GitHub's six-hour ceiling on one hung step. -**A workflow the diff adds, or whose trigger, matrix, or local mirror the diff changes, updates -two places in `skills/internal/preflight.md` (repo root) in the same change: its row in -sec."What CI runs (per-PR + nightly)", which names its trigger and what the lane runs, and its -own section - the heading beginning `## .yml`; a lane with one section per matrix arm -(`build.yml`) corrects the arm the diff changes - which names its local mirror or says it has -none.** A lane missing or wrong in either place is one nobody mirrors before a push. +**A workflow the diff adds, or whose trigger, matrix, or local mirror the diff changes, adds or +corrects its row in sec."What CI runs (per-PR + nightly)" of `skills/internal/preflight.md` +(repo root) in the same change: the row names its trigger and what the lane runs.** A lane the +table does not list, or lists wrong, is one nobody mirrors before a push. + +**The same diff adds or corrects that workflow's own section there - the heading beginning +`## .yml` - which names its local mirror or says it has none.** A workflow carrying +one section per job (`build.yml`) gets the section for the job the diff changes. **A per-PR check leaves the per-PR path only to the nightly cron (`github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'`), and the diff either names the diff --git a/include/vecmath/CLAUDE.md b/include/vecmath/CLAUDE.md index 9c97b78944..aa614b3ac0 100644 --- a/include/vecmath/CLAUDE.md +++ b/include/vecmath/CLAUDE.md @@ -8,8 +8,9 @@ transforms, physics, BVH traversal, culling, animation, etc. A backend file carries a header block stating the backend contract - which SSE/NEON semantics it matches and where it deviates - plus one-line mechanism comments at sites whose intrinsic -choice or lane order is not readable from the code. These headers are authored here and -contributed upstream to Dagor Engine, so a backend reads like its siblings. +choice or lane order is not readable from the code. The header block and the mechanism comments +are written in this repo and travel upstream to Dagor Engine with the backend, so every backend +file - including the ones this repo adds - reads like its siblings. ## Key Types (dag_vecMathDecl.h) - `vec4f` / `vec3f` -- 128-bit float vector (__m128 on SSE, float32x4_t on NEON, a clang typed vector on wasm, a 16-byte struct on scalar) diff --git a/modules/dasImgui/REVIEW.md b/modules/dasImgui/REVIEW.md index 996713a28a..b97bf59e74 100644 --- a/modules/dasImgui/REVIEW.md +++ b/modules/dasImgui/REVIEW.md @@ -13,7 +13,10 @@ under `modules/dasImgui/bind/` or `modules/dasImgui/src/`, runs the test suite o host OS before the PR: `preflight --only imgui`** (the per-OS exclude split: module `CLAUDE.md` sec. Tests). -**A diff that changes a `-m` feature flag in this folder's `CMakeLists.txt` `IMGUI_WASM_FLAGS`, -or the `DAS_IMGUI_WASM_RELAXED_SIMD` default, makes the matching change to `web/CMakeLists.txt`'s -`add_compile_options` and `DAS_WASM_RELAXED_SIMD` in the same change.** Neither build inherits -the other's flags. +**A diff that flips the `DAS_IMGUI_WASM_RELAXED_SIMD` default to ON, or appends `-mrelaxed-simd` +to `IMGUI_WASM_FLAGS` unconditionally, is a defect.** Safari and every iOS browser refuse a whole +module carrying one relaxed opcode, so the page fails to load rather than running slower. + +**A diff that changes a `-m` feature flag in this folder's `CMakeLists.txt` `IMGUI_WASM_FLAGS` +makes the matching change to `web/CMakeLists.txt`'s `add_compile_options` in the same change.** +Neither build inherits the other's flags. diff --git a/skills/comment_style_hygiene.md b/skills/comment_style_hygiene.md index 95f291aa32..2bdd565c00 100644 --- a/skills/comment_style_hygiene.md +++ b/skills/comment_style_hygiene.md @@ -33,11 +33,11 @@ added are not. rules is fork drift. A folder is vendored when it is a `3rdparty/` tree or its own `CLAUDE.md` names an upstream and records the synced revision; inside one, the files that `CLAUDE.md` marks as written or maintained here - fork-local hooks, this repo's - own docs - answer to these rules, and every file it leaves to upstream does not. The - outbound direction is the same rule: a folder whose `CLAUDE.md` names the upstream project - its files are contributed to, and states the comment shape they keep there, answers to that - shape for those files (`include/vecmath/`); the deletion test still settles anything the - stated shape does not cover. + own docs - answer to these rules, and every file it leaves to upstream does not. +- **Code contributed upstream keeps the upstream's shape.** A folder whose `CLAUDE.md` names + the project its files are contributed to, and states the comment shape they keep there, + answers to that shape for those files; the deletion test settles anything the stated shape + does not cover. Rules marked *(lintable)* are mechanical enough for a lint to enforce; where a rule notes an existing lint, the lint compels - an unnoted rule is the reviewer's. diff --git a/web/REVIEW.md b/web/REVIEW.md index d1bac95626..7161fdfa5d 100644 --- a/web/REVIEW.md +++ b/web/REVIEW.md @@ -32,6 +32,5 @@ refuse a whole module carrying one relaxed opcode, so the page fails to load rat running slower. **A diff that changes a `-m` feature flag in this folder's `CMakeLists.txt` -`add_compile_options`, or the `DAS_WASM_RELAXED_SIMD` default, makes the matching change to -`modules/dasImgui/CMakeLists.txt`'s `IMGUI_WASM_FLAGS` and `DAS_IMGUI_WASM_RELAXED_SIMD` in the -same change.** Neither build inherits the other's flags. +`add_compile_options` makes the matching change to `modules/dasImgui/CMakeLists.txt`'s +`IMGUI_WASM_FLAGS` in the same change.** Neither build inherits the other's flags.