diff --git a/.github/workflows/REVIEW.md b/.github/workflows/REVIEW.md index 3e55ae3e07..a0c677a10d 100644 --- a/.github/workflows/REVIEW.md +++ b/.github/workflows/REVIEW.md @@ -19,10 +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, 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, 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 @@ -31,9 +35,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 db44fe83c0..7b55477783 100644 --- a/.github/workflows/wasm_build.yml +++ b/.github/workflows/wasm_build.yml @@ -151,8 +151,10 @@ 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 + # 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 @@ -163,6 +165,8 @@ jobs: # 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 ########################################################### 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 8bb2007147..8506f61172 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, 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 + #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,12 @@ #include typedef float32x4_t vec4f; typedef int32x4_t vec4i; +#elif defined(_TARGET_SIMD_WASM) + // 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))); #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..aa614b3ac0 100644 --- a/include/vecmath/CLAUDE.md +++ b/include/vecmath/CLAUDE.md @@ -1,14 +1,20 @@ # 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. +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. 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 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 +31,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 | @@ -82,9 +89,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/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..f62f15678d 100644 --- a/include/vecmath/dag_vecMath_double.h +++ b/include/vecmath/dag_vecMath_double.h @@ -332,6 +332,127 @@ 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; } + +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_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))); + 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..7efcf529c4 --- /dev/null +++ b/include/vecmath/dag_vecMath_wasm.h @@ -0,0 +1,630 @@ +// +// 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 fuse under +// -mrelaxed-simd: the product is not rounded before the 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)) +#define VECMATH_WASM_D(a) ((vecmath_f64x2)(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(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))); } +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) { 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))); } +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); } +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_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))); } +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)); } + +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_dot4_x(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 < 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) +{ 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/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/CMakeLists.txt b/modules/dasImgui/CMakeLists.txt index a7ccce4457..1cd4ad110b 100644 --- a/modules/dasImgui/CMakeLists.txt +++ b/modules/dasImgui/CMakeLists.txt @@ -432,10 +432,15 @@ 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})") + # 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 - -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/modules/dasImgui/REVIEW.md b/modules/dasImgui/REVIEW.md index b1a3ad2472..b97bf59e74 100644 --- a/modules/dasImgui/REVIEW.md +++ b/modules/dasImgui/REVIEW.md @@ -12,3 +12,11 @@ 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 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/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..2bdd565c00 100644 --- a/skills/comment_style_hygiene.md +++ b/skills/comment_style_hygiene.md @@ -34,6 +34,10 @@ added are not. `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. +- **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/skills/internal/preflight.md b/skills/internal/preflight.md index 88d9c745f6..9c3a0ce946 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,24 @@ 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 +(`"$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 c4ec38aa1e..a2b3e02b7c 100644 --- a/tests-cpp/big/vecmath_backend/CMakeLists.txt +++ b/tests-cpp/big/vecmath_backend/CMakeLists.txt @@ -32,3 +32,25 @@ if(NOT DAS_VECMATH_SCALAR) set_tests_properties(vecmath_native PROPERTIES LABELS "small") 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; 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) + list(APPEND _wasm_arms test_vecmath_native) + endif() + foreach(_arm ${_wasm_arms}) + set_target_properties(${_arm} PROPERTIES + 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 51e6f05374..24d9f49d44 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 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" @@ -30,6 +32,13 @@ #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_X_IS_PACKED 1 +#define VECMATH_TEST_CVT_SATURATES 1 +#endif static int g_failed = 0; @@ -103,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(_TARGET_SIMD_NEON) +#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)); @@ -124,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(_TARGET_SIMD_NEON) +#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)); @@ -485,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(_TARGET_SIMD_NEON) // NEON 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 @@ -502,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 0f1de7da76..c4f53407f4 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -31,8 +31,13 @@ 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 wasm backend (include/vecmath/dag_vecMath_wasm.h) selects itself on __wasm_simd128__; no -msse* compat layer is needed. +# 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() # 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 diff --git a/web/REVIEW.md b/web/REVIEW.md index 8a5268e218..7161fdfa5d 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 @@ -26,3 +25,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 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` makes the matching change to `modules/dasImgui/CMakeLists.txt`'s +`IMGUI_WASM_FLAGS` in the same change.** Neither build inherits the other's flags.