diff --git a/README.md b/README.md index 8022f48..6ae4f3f 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,30 @@ A header-only C++20 SIMD math library for high-performance 4D vector arithmetic. -tenvex uses **expression templates** — a C++ template metaprogramming technique that enables lazy, zero-overhead evaluation of chained vector operations. Complex expressions like `norm(a + b * 2.0f) * dot(b, c) + c * 3.0f` compile down to a single fused sequence of SSE/AVX intrinsics with no intermediate temporaries. +tenvex uses **expression templates** - a C++ template metaprogramming technique that enables lazy, zero-overhead evaluation of chained vector operations. A compound expression such as `norm3(a + b * 2.0f) * dot3(b, c) + c * 3.0f` collapses, at the point of assignment, into a single fused sequence of SSE4.1 intrinsics with no intermediate temporaries. + +## License + +BSD 3-Clause License. Copyright (c) 2026 Artem Zyktin. See [LICENSE.txt](https://claude.ai/chat/LICENSE.txt). + +## Status + +- **x86-64 / SSE4.1** - implemented and tested. +- **ARM64 / NEON** - planned, not yet implemented. ## Features -- Header-only — just include `tenvex.h` -- Expression template engine with zero-cost abstractions -- All arithmetic backed by SSE/AVX intrinsics (`__m128`) -- C++20 concepts enforce type safety at compile time -- Smart storage policy: small trivial types stored by value, others by `const&` -- Supported operations: `+`, `-`, `*` (scalar), `dot`, `cross`, `norm`, `magnitude` +- Header-only - just include `tenvex.h`. +- Expression-template engine with zero-cost abstractions: nodes are lazy and fuse on assignment. +- C++20 concepts (`expression`, `vec_expr`, `scalar_expr`) enforce type safety at compile time. +- Storage policy: small trivially-copyable nodes are stored by value, larger ones by `const&`, which also avoids dangling references to temporary sub-expressions. +- Operations: `+`, `-`, `*` (scalar), `/` (by scalar), `dot3`, `cross3`, `norm3`, `magnitude3`, plus `==` and `approx_eq`. ## Requirements -- C++20 compiler (MSVC, GCC, Clang) -- x64 CPU with SSE2 support -- [Premake5](https://premake.github.io/) (binaries included for Windows, Linux, macOS) +- A C++20 compiler (MSVC, GCC, or Clang). +- An x64 CPU with **SSE4.1** support +- Premake for generating the test build (binaries are bundled for Windows, Linux, and macOS). ## Getting Started @@ -38,35 +46,36 @@ vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f }; vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f }; vec4 c = { 1.0f, 0.0f, 1.0f, 0.0f }; -// Basic arithmetic -vec4 sum = a + b; -vec4 diff = a - b; -vec4 scaled = a * 2.0f; -vec4 scaled2 = 3.0f * b; +// Arithmetic +vec4 sum = a + b; +vec4 diff = a - b; +vec4 scaled = a * 2.0f; // vector * scalar +vec4 scaled2 = 3.0f * b; // scalar * vector +vec4 halved = a / 2.0f; // vector / scalar -// Vector operations -float d = dot(a, b); -vec4 cr = cross(a, b); -vec4 n = norm(a); -float mag = magnitude(a); +// Vector operations (3-component; the w / homogeneous lane is handled explicitly) +float d = dot3(a, b); // 3D dot product (ignores w) +vec4 cr = cross3(a, b); // 3D cross product, result w = 0 +vec4 n = norm3(a); // normalize xyz, w preserved +float mag = magnitude3(a); // 3D length (ignores w) -// Compound expressions — evaluated lazily, no temporaries -vec4 result = norm(a + b * 2.0f) * dot(b, c) + c * 3.0f; +// Compound expression - evaluated lazily, no temporaries, fused on assignment +vec4 result = norm3(a + b * 2.0f) * dot3(b, c) + c * 3.0f; ``` -### `vec4` Member Functions +`dot3` and `magnitude3` return scalar-expression nodes (`Dot3`, `Magn3`) that are implicitly convertible to `float` (the scalar is extracted from lane 0), so both `float d = dot3(a, b);` and using them inside a larger expression work. + +### `vec4` + +`vec4` is a 16-byte, `alignas(16)` value type. It can be default-constructed, built from components (with `w` defaulting to `0`), or constructed from any vector expression (which triggers evaluation): ```cpp -vec4 v = { 1.0f, 2.0f, 3.0f, 4.0f }; +vec4 zero; // default +vec4 v = { 1.0f, 2.0f, 3.0f }; // w defaults to 0 +vec4 p = { 1.0f, 2.0f, 3.0f, 1.0f }; // explicit w +vec4 r = a + b * 2.0f; // built from an expression (evaluates here) -float x = v.x(); // component accessors -float y = v.y(); -float z = v.z(); -float w = v.w(); - -auto arr = v.data(); // std::array -float mag = v.magnitude(); // vector length -vec4 n = v.norm(); // unit vector +vf4 raw = v.eval(); // underlying __m128 ``` ### Comparison @@ -75,57 +84,72 @@ vec4 n = v.norm(); // unit vector vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f }; -bool exact = (a == b); // exact equality -bool approx = approx_eq(a, b); // epsilon = 1e-3 (default) -bool approx = approx_eq(a, b, 1e-6f); // custom epsilon +bool exact = (a == b); // exact, all-lane equality +bool approx1 = approx_eq(a, b); // default epsilon = 1e-6f +bool approx2 = approx_eq(a, b, 1e-3f); // custom epsilon ``` ## Architecture -### Expression Templates +### Two layers -Every operation returns a lazy expression node rather than evaluating immediately. Nodes inherit from `Expr` via CRTP and expose a single `__m128 eval() const` method. Evaluation only happens when the result is assigned to a `vec4`. +- **Eager kernel layer** (`expressions/core.h`, namespace `tnvx::detail`): plain `TNVX_INLINE` functions over `vf4` (`add`, `sub`, `mul`, `div`, `dot3`, `magnitude3`, `norm3`, `cross3`, `scalar`, `eq`, `approx_eq`). This is the only place intrinsics live, and the only place the SIMD backend is selected. +- **Expression layer** (everything else in namespace `tnvx`): lazy nodes that compose and delegate down to the kernels. This layer is architecture-neutral - it only ever sees `vf4`. + +### Expression templates + +Every operation returns a lazy node rather than evaluating immediately. Nodes inherit from `Expr` via CRTP (which provides `self()`) and expose a `vf4 eval() const` method. Evaluation happens only when the result is assigned to a `vec4`, or - for scalar nodes - when converted to `float`. ``` -vec4 result = norm(a + b * 2.0f) * dot(b, c) + c * 3.0f; -// └─ Mul>>, Mul, Mul>> -// ↑ entire tree collapsed to ~5 SSE instructions at assignment +vec4 result = norm3(a + b * 2.0f) * dot3(b, c) + c * 3.0f; +// Add< Mul< Norm3< Add> >, Scalar >, Mul > +// ^ the whole tree collapses to a short fused SSE4.1 sequence at assignment ``` -### C++20 Concepts +### C++20 concepts -| Concept | Meaning | -|----------------------|----------------------------------------------| -| `expression` | Any `Expr` subtype with `__m128 eval()` | -| `vector_expression` | An `expression` that is not a `Scalar` | +| Concept | Meaning | +| ---------------- | ----------------------------------------------------------------------- | +| `expression` | Derives from `Expr` and has `eval() -> vf4` and `self() -> const T&` | +| `vec_expr` | An `expression` marked as a vector expression (`is_vec_expr`) | +| `scalar_expr` | An `expression` marked as a scalar expression (`is_scalar_expr`) | -### Storage Policy +### Storage policy -To avoid dangling references to temporary expression nodes, storage is chosen automatically: +To keep small nodes register-friendly and to avoid dangling references to temporary sub-expressions, each node's operands are stored by value or by `const&` automatically: ```cpp -// trivially copyable & destructible & sizeof <= 16 → stored by value -// otherwise → stored as const T& +// trivially copyable && trivially destructible && sizeof(T) <= 16 -> stored by value +// otherwise -> stored as const T& template -using tnvx_type_storage = std::conditional_t, T, const T&>; +using tnvx_ref_or_value_t = + std::conditional_t, T, const T&>; ``` -### Operation Nodes +### Operation nodes -| Node | Intrinsic(s) | Notes | -|-----------------|-------------------------------------|------------------------------------| -| `Add` | `_mm_add_ps` | | -| `Sub` | `_mm_sub_ps` | | -| `Mul` | `_mm_mul_ps` | Scalar multiplication only | -| `Dot` | `_mm_mul_ps` + horizontal shuffle | Result broadcast to all 4 lanes | -| `Cross` | `_mm_mul_ps` + `_mm_sub_ps` + shuffles | 3-component; w = 0 | -| `Norm` | `_mm_div_ps` + `_mm_sqrt_ps` | Divide by magnitude | -| `Magnitude` | `_mm_sqrt_ss` | Scalar result | -| `Scalar` | `_mm_set1_ps` | Broadcasts float to all 4 lanes | +| Node | Concept | Intrinsics | Notes | +| ------------- | ------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------- | +| `Scalar` | `scalar_expr` | `_mm_set_ps1` | Broadcasts a `float` to all 4 lanes | +| `Add` | `vec_expr` | `_mm_add_ps` | | +| `Sub` | `vec_expr` | `_mm_sub_ps` | | +| `Mul` | vec / scalar | `_mm_mul_ps` | Scalar multiply: `vec * float`, `float * vec`, `scalar * scalar` | +| `Div` | `vec_expr` | `_mm_div_ps` | Vector divided by a scalar (`float`) | +| `Dot3` | `scalar_expr` | `_mm_dp_ps` (mask `0x7F`) | 3-component dot (ignores w), broadcast to all lanes; convertible to `float` | +| `Cross3` | `vec_expr` | `_mm_shuffle_ps` + `_mm_mul_ps` + `_mm_sub_ps` | 3-component cross product; result `w = 0` | +| `Norm3` | `vec_expr` | `_mm_dp_ps` + `_mm_sqrt_ps` + `_mm_div_ps` + `_mm_blend_ps` | Normalizes xyz by the 3D magnitude; preserves w | +| `Magn3` | `scalar_expr` | `_mm_sqrt_ps(_mm_dp_ps(..., 0x7F))` | 3-component length (ignores w), broadcast; convertible to `float` | + +Comparison helpers (free functions in `tnvx`, backed by `tnvx::detail`): + +| Function | Intrinsics | Notes | +| ---------------------------- | --------------------------------------- | ------------------------------ | +| `operator==` | `_mm_cmpeq_ps` + `_mm_movemask_ps` | Exact, all-lane equality | +| `approx_eq(a, b, eps=1e-6f)` | `_mm_andnot_ps` + `_mm_cmple_ps` + mask | Per-lane abs difference <= eps | ## Building -The build system uses [Premake5](https://premake.github.io/). Premake5 binaries are included in `premake/bin/` for all platforms. +The build uses [Premake5](https://premake.github.io/); binaries are bundled in `premake/bin/` for all platforms. The workspace is x64-only and uses C++20, with `debug` and `release` configurations and `tenvex_tests` as the start project. ### Windows (Visual Studio 2022) @@ -133,11 +157,9 @@ The build system uses [Premake5](https://premake.github.io/). Premake5 binaries scripts\config-vs2022.bat ``` -Then open the generated solution in `build/` and build the `tenvex_tests` project. +Then open the generated solution under `build/` and build the `tenvex_tests` project. -### Other Platforms - -Run the appropriate Premake5 binary manually: +### Linux / macOS ```sh # Linux @@ -145,48 +167,21 @@ Run the appropriate Premake5 binary manually: # macOS ./premake/bin/macos/premake5 gmake2 -``` -Then: - -```sh -make config=debug +# then +make config=debug # or: make config=release ``` Build output is written to `bin//x64//output/`. ## Running Tests -The test suite uses Google Test (vendored in `thirdparty/gtest/`) and covers 18 cases: +The test suite uses Google Test (vendored in `thirdparty/gtest/`) and currently covers 19 cases: -| Category | Tests | -|--------------------|-----------------------------------------------------------------------| -| Basic operations | `add`, `sub`, `mult_a`, `mult_b`, `dot`, `cross`, `norm_a/b`, `magnitude_a/b` | -| Compound expressions | `expression_a`, `expression_b`, `expression_c` | -| Math properties | `cross_anticommutative`, `dot_commutative`, `norm_magnitude`, `dot_perpendicular`, `cross_parallel` | +| Category | Tests | +| -------------------- | ---------------------------------------------------------------------------------------------------------- | +| Basic operations | `add`, `sub`, `mult_a`, `mult_b`, `div`, `dot3`, `cross`, `norm_a`, `norm_b`, `magnitude_a`, `magnitude_b` | +| Compound expressions | `expression_a`, `expression_b`, `expression_c` | +| Math properties | `cross_anticommutative`, `dot_commutative`, `norm_magnitude`, `dot_perpendicular`, `cross_parallel` | Build and run `tenvex_tests` from the generated project or makefile. - -## Project Structure - -``` -tenvex/ -├── src/ -│ ├── tenvex/ # Library headers (header-only) -│ │ ├── tenvex.h # Single public include -│ │ ├── vec4.h / vec4_impl.hpp -│ │ └── internal/ # Expression nodes and infrastructure -│ └── tests/ -│ └── vec4_tests.cpp # Google Test suite -├── thirdparty/ -│ └── gtest/ # Vendored Google Test -├── premake/ -│ └── bin/ # Premake5 binaries (Windows, Linux, macOS) -├── scripts/ -│ └── config-vs2022.bat # VS2022 project generation -└── premake5.lua # Build configuration -``` - -## License - -BSD 3-Clause License. Copyright (c) 2026 Artem Zyktin. See [LICENSE.txt](LICENSE.txt).