Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions .github/workflows/REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`## <workflow>.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
Expand All @@ -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 <ids>; do` loop, never inline.** `examples/games/REVIEW.das` (repo root) reads the
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/wasm_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 16 additions & 3 deletions include/daScript/daScriptC.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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
Expand All @@ -48,6 +55,12 @@
#include <arm_neon.h>
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 <stdint.h>
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)
Expand Down
24 changes: 16 additions & 8 deletions include/vecmath/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 |

Expand Down Expand Up @@ -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
Expand Down
25 changes: 15 additions & 10 deletions include/vecmath/README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,32 +12,37 @@ 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.

## 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`.
Comment thread
borisbat marked this conversation as resolved.
- 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
Expand Down
2 changes: 2 additions & 0 deletions include/vecmath/dag_vecMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 38 additions & 3 deletions include/vecmath/dag_vecMathDecl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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
Expand Down Expand Up @@ -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 <stdint.h>
#include <wasm_simd128.h>

//! 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 <stdint.h>

Expand Down
2 changes: 1 addition & 1 deletion include/vecmath/dag_vecMath_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)); }
Expand Down
2 changes: 1 addition & 1 deletion include/vecmath/dag_vecMath_const.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) };
Expand Down
Loading
Loading